diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /alpine/mailcmd.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'alpine/mailcmd.c')
-rw-r--r-- | alpine/mailcmd.c | 9472 |
1 files changed, 9472 insertions, 0 deletions
diff --git a/alpine/mailcmd.c b/alpine/mailcmd.c new file mode 100644 index 00000000..fa781556 --- /dev/null +++ b/alpine/mailcmd.c @@ -0,0 +1,9472 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: mailcmd.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2009 University of Washington + * Copyright 2013 Eduardo Chappa + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * ======================================================================== + */ + +/*====================================================================== + mailcmd.c + The meat and pototoes of mail processing here: + - initial command processing and dispatch + - save message + - capture address off incoming mail + - jump to specific numbered message + - open (broach) a new folder + - search message headers (where is) command + ====*/ + + +#include "headers.h" +#include "mailcmd.h" +#include "status.h" +#include "mailview.h" +#include "flagmaint.h" +#include "listsel.h" +#include "keymenu.h" +#include "alpine.h" +#include "mailpart.h" +#include "mailindx.h" +#include "folder.h" +#include "reply.h" +#include "help.h" +#include "titlebar.h" +#include "signal.h" +#include "radio.h" +#include "pipe.h" +#include "send.h" +#include "takeaddr.h" +#include "roleconf.h" +#include "smime.h" +#include "../pith/state.h" +#include "../pith/msgno.h" +#include "../pith/store.h" +#include "../pith/thread.h" +#include "../pith/flag.h" +#include "../pith/sort.h" +#include "../pith/maillist.h" +#include "../pith/save.h" +#include "../pith/pipe.h" +#include "../pith/news.h" +#include "../pith/util.h" +#include "../pith/sequence.h" +#include "../pith/keyword.h" +#include "../pith/stream.h" +#include "../pith/mailcmd.h" +#include "../pith/hist.h" +#include "../pith/list.h" +#include "../pith/icache.h" +#include "../pith/busy.h" +#include "../pith/mimedesc.h" +#include "../pith/pattern.h" +#include "../pith/tempfile.h" +#include "../pith/search.h" +#include "../pith/margin.h" +#ifdef _WINDOWS +#include "../pico/osdep/mswin.h" +#endif + +/* + * Internal Prototypes + */ +int cmd_flag(struct pine *, MSGNO_S *, int); +int cmd_flag_prompt(struct pine *, struct flag_screen *, int); +void free_flag_table(struct flag_table **); +int cmd_reply(struct pine *, MSGNO_S *, int); +int cmd_forward(struct pine *, MSGNO_S *, int); +int cmd_bounce(struct pine *, MSGNO_S *, int); +int cmd_save(struct pine *, MAILSTREAM *, MSGNO_S *, int, CmdWhere); +void role_compose(struct pine *); +void cmd_expunge(struct pine *, MAILSTREAM *, MSGNO_S *); +int cmd_export(struct pine *, MSGNO_S *, int, int); +char *cmd_delete_action(struct pine *, MSGNO_S *, CmdWhere); +char *cmd_delete_view(struct pine *, MSGNO_S *); +char *cmd_delete_index(struct pine *, MSGNO_S *); +long get_level(int, UCS, SCROLL_S *); +long closest_jump_target(long, MAILSTREAM *, MSGNO_S *, int, CmdWhere, char *, size_t); +int update_folder_spec(char *, size_t, char *); +int cmd_print(struct pine *, MSGNO_S *, int, CmdWhere); +int cmd_pipe(struct pine *, MSGNO_S *, int); +STORE_S *list_mgmt_text(RFC2369_S *, long); +void list_mgmt_screen(STORE_S *); +int aggregate_select(struct pine *, MSGNO_S *, int, CmdWhere); +int select_by_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **); +int select_by_thrd_number(MAILSTREAM *, MSGNO_S *, SEARCHSET **); +int select_by_date(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); +int select_by_text(MAILSTREAM *, MSGNO_S *, long, SEARCHSET **); +int select_by_size(MAILSTREAM *, SEARCHSET **); +SEARCHSET *visible_searchset(MAILSTREAM *, MSGNO_S *); +int select_by_status(MAILSTREAM *, SEARCHSET **); +int select_by_rule(MAILSTREAM *, SEARCHSET **); +int select_by_thread(MAILSTREAM *, MSGNO_S *, SEARCHSET **); +char *choose_a_rule(int); +int select_by_keyword(MAILSTREAM *, SEARCHSET **); +char *choose_a_keyword(void); +int select_sort(struct pine *, int, SortOrder *, int *); +int print_index(struct pine *, MSGNO_S *, int); + + + +/* + * List of Select options used by apply_* functions... + */ +static char *sel_pmt1 = N_("ALTER message selection : "); +ESCKEY_S sel_opts1[] = { + /* TRANSLATORS: these are keymenu names for selecting. Broaden selection means + we will add more messages to the selection, Narrow selection means we will + remove some selections (like a logical AND instead of logical OR), and Flip + Selected means that all the messages that are currently selected become unselected, + and all the unselected messages become selected. */ + {'a', 'a', "A", N_("unselect All")}, + {'c', 'c', "C", NULL}, + {'b', 'b', "B", N_("Broaden selctn")}, + {'n', 'n', "N", N_("Narrow selctn")}, + {'f', 'f', "F", N_("Flip selected")}, + {-1, 0, NULL, NULL} +}; + + +#define SEL_OPTS_THREAD 9 /* index number of "tHread" */ +#define SEL_OPTS_THREAD_CH 'h' + +char *sel_pmt2 = "SELECT criteria : "; +static ESCKEY_S sel_opts2[] = { + /* TRANSLATORS: very short descriptions of message selection criteria. Select Cur + means select the currently highlighted message; select by Number is by message + number; Status is by status of the message, for example the message might be + New or it might be Unseen or marked Important; Size has the Z upper case because + it is a Z command; Keyword is an alpine keyword that has been set by the user; + and Rule is an alpine rule */ + {'a', 'a', "A", N_("select All")}, + {'c', 'c', "C", N_("select Cur")}, + {'n', 'n', "N", N_("Number")}, + {'d', 'd', "D", N_("Date")}, + {'t', 't', "T", N_("Text")}, + {'s', 's', "S", N_("Status")}, + {'z', 'z', "Z", N_("siZe")}, + {'k', 'k', "K", N_("Keyword")}, + {'r', 'r', "R", N_("Rule")}, + {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, + {-1, 0, NULL, NULL} +}; + + +static ESCKEY_S sel_opts3[] = { + /* TRANSLATORS: these are operations we can do on a set of selected messages. + Del is Delete; Undel is Undelete; TakeAddr means to Take some Addresses into + the address book; Save means to save the messages into another alpine folder; + Export means to copy the messages to a file outside of alpine, external to + alpine's world. */ + {'d', 'd', "D", N_("Del")}, + {'u', 'u', "U", N_("Undel")}, + {'r', 'r', "R", N_("Reply")}, + {'f', 'f', "F", N_("Forward")}, + {'%', '%', "%", N_("Print")}, + {'t', 't', "T", N_("TakeAddr")}, + {'s', 's', "S", N_("Save")}, + {'e', 'e', "E", N_("Export")}, + { -1, 0, NULL, NULL}, + { -1, 0, NULL, NULL}, + { -1, 0, NULL, NULL}, + { -1, 0, NULL, NULL}, + { -1, 0, NULL, NULL}, + { -1, 0, NULL, NULL}, + { -1, 0, NULL, NULL}, + {-1, 0, NULL, NULL} +}; + +static ESCKEY_S sel_opts4[] = { + {'a', 'a', "A", N_("select All")}, + /* TRANSLATORS: select currrently highlighted message Thread */ + {'c', 'c', "C", N_("select Curthrd")}, + {'n', 'n', "N", N_("Number")}, + {'d', 'd', "D", N_("Date")}, + {'t', 't', "T", N_("Text")}, + {'s', 's', "S", N_("Status")}, + {'z', 'z', "Z", N_("siZe")}, + {'k', 'k', "K", N_("Keyword")}, + {'r', 'r', "R", N_("Rule")}, + {SEL_OPTS_THREAD_CH, 'h', "H", N_("tHread")}, + {-1, 0, NULL, NULL} +}; + + +static char *sel_flag = + N_("Select New, Deleted, Answered, Forwarded, or Important messages ? "); +static char *sel_flag_not = + N_("Select NOT New, NOT Deleted, NOT Answered, NOT Forwarded or NOT Important msgs ? "); +static ESCKEY_S sel_flag_opt[] = { + /* TRANSLATORS: When selecting messages by message Status these are the + different types of Status you can select on. Is the message New, Recent, + and so on. Not means flip the meaning of the selection to the opposite + thing, so message is not New or not Important. */ + {'n', 'n', "N", N_("New")}, + {'*', '*', "*", N_("Important")}, + {'d', 'd', "D", N_("Deleted")}, + {'a', 'a', "A", N_("Answered")}, + {'f', 'f', "F", N_("Forwarded")}, + {-2, 0, NULL, NULL}, + {'!', '!', "!", N_("Not")}, + {-2, 0, NULL, NULL}, + {'r', 'r', "R", N_("Recent")}, + {'u', 'u', "U", N_("Unseen")}, + {-1, 0, NULL, NULL} +}; + + +static ESCKEY_S sel_date_opt[] = { + {0, 0, NULL, NULL}, + /* TRANSLATORS: options when selecting messages by Date */ + {ctrl('P'), 12, "^P", N_("Prev Day")}, + {ctrl('N'), 13, "^N", N_("Next Day")}, + {ctrl('X'), 11, "^X", N_("Cur Msg")}, + {ctrl('W'), 14, "^W", N_("Toggle When")}, + {KEY_UP, 12, "", ""}, + {KEY_DOWN, 13, "", ""}, + {-1, 0, NULL, NULL} +}; + + +static char *sel_text = + N_("Select based on To, From, Cc, Recip, Partic, Subject fields or All msg text ? "); +static char *sel_text_not = + N_("Select based on NOT To, From, Cc, Recip, Partic, Subject or All msg text ? "); +static ESCKEY_S sel_text_opt[] = { + /* TRANSLATORS: Select messages based on the text contained in the From line, or + the Subject line, and so on. */ + {'f', 'f', "F", N_("From")}, + {'s', 's', "S", N_("Subject")}, + {'t', 't', "T", N_("To")}, + {'a', 'a', "A", N_("All Text")}, + {'c', 'c', "C", N_("Cc")}, + {'!', '!', "!", N_("Not")}, + {'r', 'r', "R", N_("Recipient")}, + {'p', 'p', "P", N_("Participant")}, + {'b', 'b', "B", N_("Body")}, + {'h', 'h', "H", N_("Header")}, + {-1, 0, NULL, NULL} +}; + +static ESCKEY_S choose_action[] = { + {'c', 'c', "C", N_("Compose")}, + {'r', 'r', "R", N_("Reply")}, + {'f', 'f', "F", N_("Forward")}, + {'b', 'b', "B", N_("Bounce")}, + {-1, 0, NULL, NULL} +}; + +static char *select_num = + N_("Enter comma-delimited list of numbers (dash between ranges): "); + +static char *select_size_larger_msg = + N_("Select messages with size larger than: "); + +static char *select_size_smaller_msg = + N_("Select messages with size smaller than: "); + +static char *sel_size_larger = N_("Larger"); +static char *sel_size_smaller = N_("Smaller"); +static ESCKEY_S sel_size_opt[] = { + {0, 0, NULL, NULL}, + {ctrl('W'), 14, "^W", NULL}, + {-1, 0, NULL, NULL} +}; + +static ESCKEY_S sel_key_opt[] = { + {0, 0, NULL, NULL}, + {ctrl('T'), 14, "^T", N_("To List")}, + {0, 0, NULL, NULL}, + {'!', '!', "!", N_("Not")}, + {-1, 0, NULL, NULL} +}; + +static ESCKEY_S flag_text_opt[] = { + /* TRANSLATORS: these are types of flags (markers) that the user can + set. For example, they can flag the message as an important message. */ + {'n', 'n', "N", N_("New")}, + {'*', '*', "*", N_("Important")}, + {'d', 'd', "D", N_("Deleted")}, + {'a', 'a', "A", N_("Answered")}, + {'f', 'f', "F", N_("Forwarded")}, + {'!', '!', "!", N_("Not")}, + {ctrl('T'), 10, "^T", N_("To Flag Details")}, + {-1, 0, NULL, NULL} +}; + + +/*---------------------------------------------------------------------- + The giant switch on the commands for index and viewing + + Input: command -- The command char/code + in_index -- flag indicating command is from index + orig_command -- The original command typed before pre-processing + Output: force_mailchk -- Set to tell caller to force call to new_mail(). + + Result: Manifold + + Returns 1 if the message number or attachment to show changed + ---*/ +int +process_cmd(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, + int command, CmdWhere in_index, int *force_mailchk) +{ + int question_line, a_changed, flags = 0, ret, j; + int notrealinbox; + long new_msgno, del_count, old_msgno, i; + long start; + char *newfolder, prompt[MAX_SCREEN_COLS+1]; + CONTEXT_S *tc; + MESSAGECACHE *mc; +#if defined(DOS) && !defined(_WINDOWS) + extern long coreleft(); +#endif + + dprint((4, "\n - process_cmd(cmd=%d) -\n", command)); + + question_line = -FOOTER_ROWS(state); + state->mangled_screen = 0; + state->mangled_footer = 0; + state->mangled_header = 0; + state->next_screen = SCREEN_FUN_NULL; + old_msgno = mn_get_cur(msgmap); + a_changed = FALSE; + *force_mailchk = 0; + + switch (command) { + /*------------- Help --------*/ + case MC_HELP : + /* + * We're not using the h_mail_view portion of this right now because + * that call is being handled in scrolltool() before it gets + * here. Leave it in case we change how it works. + */ + helper((in_index == MsgIndx) + ? h_mail_index + : (in_index == View) + ? h_mail_view + : h_mail_thread_index, + (in_index == MsgIndx) + ? _("HELP FOR MESSAGE INDEX") + : (in_index == View) + ? _("HELP FOR MESSAGE TEXT") + : _("HELP FOR THREAD INDEX"), + HLPD_NONE); + dprint((4,"MAIL_CMD: did help command\n")); + state->mangled_screen = 1; + break; + + + /*--------- Return to main menu ------------*/ + case MC_MAIN : + state->next_screen = main_menu_screen; + dprint((2,"MAIL_CMD: going back to main menu\n")); + break; + + + /*------- View message text --------*/ + case MC_VIEW_TEXT : +view_text: + if(any_messages(msgmap, NULL, "to View")){ + state->next_screen = mail_view_screen; + } + + break; + + + /*------- View attachment --------*/ + case MC_VIEW_ATCH : + state->next_screen = attachment_screen; + dprint((2,"MAIL_CMD: going to attachment screen\n")); + break; + + + /*---------- Previous message ----------*/ + case MC_PREVITEM : + if(any_messages(msgmap, NULL, NULL)){ + if((i = mn_get_cur(msgmap)) > 1L){ + mn_dec_cur(stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + if(i == mn_get_cur(msgmap)){ + PINETHRD_S *thrd, *topthrd; + + if(THRD_INDX_ENABLED()){ + mn_dec_cur(stream, msgmap, MH_ANYTHD); + if(i == mn_get_cur(msgmap)) + q_status_message1(SM_ORDER, 0, 2, + _("Already on first %s in Zoomed Index"), + THRD_INDX() ? _("thread") : _("message")); + else{ + if(in_index == View + || F_ON(F_NEXT_THRD_WO_CONFIRM, state)) + ret = 'y'; + else + ret = want_to(_("View previous thread"), 'y', 'x', + NO_HELP, WT_NORM); + + if(ret == 'y'){ + q_status_message(SM_ORDER, 0, 2, + _("Viewing previous thread")); + new_msgno = mn_get_cur(msgmap); + mn_set_cur(msgmap, i); + if(unview_thread(state, stream, msgmap)){ + state->next_screen = mail_index_screen; + state->view_skipped_index = 0; + state->mangled_screen = 1; + } + + mn_set_cur(msgmap, new_msgno); + if(THRD_AUTO_VIEW() && in_index == View){ + + thrd = fetch_thread(stream, + mn_m2raw(msgmap, + new_msgno)); + if(count_lflags_in_thread(stream, thrd, + msgmap, + MN_NONE) == 1){ + if(view_thread(state, stream, msgmap, 1)){ + if(current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + state->view_skipped_index = 1; + command = MC_VIEW_TEXT; + goto view_text; + } + } + } + + j = 0; + if(THRD_AUTO_VIEW() && in_index != View){ + thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno)); + if(thrd && thrd->top) + topthrd = fetch_thread(stream, thrd->top); + + if(topthrd) + j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE); + } + + if(!THRD_AUTO_VIEW() || in_index == View || j != 1){ + if(view_thread(state, stream, msgmap, 1) + && current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + } + + state->next_screen = SCREEN_FUN_NULL; + } + else + mn_set_cur(msgmap, i); /* put it back */ + } + } + else + q_status_message1(SM_ORDER, 0, 2, + _("Already on first %s in Zoomed Index"), + THRD_INDX() ? _("thread") : _("message")); + } + } + else{ + time_t now; + + if(!IS_NEWS(stream) + && ((now = time(0)) > state->last_nextitem_forcechk)){ + *force_mailchk = 1; + /* check at most once a second */ + state->last_nextitem_forcechk = now; + } + + q_status_message1(SM_ORDER, 0, 1, _("Already on first %s"), + THRD_INDX() ? _("thread") : _("message")); + } + } + + break; + + + /*---------- Next Message ----------*/ + case MC_NEXTITEM : + if(mn_get_total(msgmap) > 0L + && ((i = mn_get_cur(msgmap)) < mn_get_total(msgmap))){ + mn_inc_cur(stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + if(i == mn_get_cur(msgmap)){ + PINETHRD_S *thrd, *topthrd; + + if(THRD_INDX_ENABLED()){ + if(!THRD_INDX()) + mn_inc_cur(stream, msgmap, MH_ANYTHD); + + if(i == mn_get_cur(msgmap)){ + if(any_lflagged(msgmap, MN_HIDE)) + any_messages(NULL, "more", "in Zoomed Index"); + else + goto nfolder; + } + else{ + if(in_index == View + || F_ON(F_NEXT_THRD_WO_CONFIRM, state)) + ret = 'y'; + else + ret = want_to(_("View next thread"), 'y', 'x', + NO_HELP, WT_NORM); + + if(ret == 'y'){ + q_status_message(SM_ORDER, 0, 2, + _("Viewing next thread")); + new_msgno = mn_get_cur(msgmap); + mn_set_cur(msgmap, i); + if(unview_thread(state, stream, msgmap)){ + state->next_screen = mail_index_screen; + state->view_skipped_index = 0; + state->mangled_screen = 1; + } + + mn_set_cur(msgmap, new_msgno); + if(THRD_AUTO_VIEW() && in_index == View){ + + thrd = fetch_thread(stream, + mn_m2raw(msgmap, + new_msgno)); + if(count_lflags_in_thread(stream, thrd, + msgmap, + MN_NONE) == 1){ + if(view_thread(state, stream, msgmap, 1)){ + if(current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + state->view_skipped_index = 1; + command = MC_VIEW_TEXT; + goto view_text; + } + } + } + + j = 0; + if(THRD_AUTO_VIEW() && in_index != View){ + thrd = fetch_thread(stream, mn_m2raw(msgmap, new_msgno)); + if(thrd && thrd->top) + topthrd = fetch_thread(stream, thrd->top); + + if(topthrd) + j = count_lflags_in_thread(stream, topthrd, msgmap, MN_NONE); + } + + if(!THRD_AUTO_VIEW() || in_index == View || j != 1){ + if(view_thread(state, stream, msgmap, 1) + && current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + } + + state->next_screen = SCREEN_FUN_NULL; + } + else + mn_set_cur(msgmap, i); /* put it back */ + } + } + else if(THREADING() + && (thrd = fetch_thread(stream, mn_m2raw(msgmap, i))) + && thrd->next + && get_lflag(stream, NULL, thrd->rawno, MN_COLL)){ + q_status_message(SM_ORDER, 0, 2, + _("Expand collapsed thread to see more messages")); + } + else + any_messages(NULL, "more", "in Zoomed Index"); + } + } + else{ + time_t now; +nfolder: + prompt[0] = '\0'; + if(IS_NEWS(stream) + || (state->context_current->use & CNTXT_INCMNG)){ + char nextfolder[MAXPATH]; + + strncpy(nextfolder, state->cur_folder, sizeof(nextfolder)); + nextfolder[sizeof(nextfolder)-1] = '\0'; + if(next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder, + state->context_current, NULL, NULL)) + strncpy(prompt, _(". Press TAB for next folder."), + sizeof(prompt)); + else + strncpy(prompt, _(". No more folders to TAB to."), + sizeof(prompt)); + + prompt[sizeof(prompt)-1] = '\0'; + } + + any_messages(NULL, (mn_get_total(msgmap) > 0L) ? "more" : NULL, + prompt[0] ? prompt : NULL); + + if(!IS_NEWS(stream) + && ((now = time(0)) > state->last_nextitem_forcechk)){ + *force_mailchk = 1; + /* check at most once a second */ + state->last_nextitem_forcechk = now; + } + } + + break; + + + /*---------- Delete message ----------*/ + case MC_DELETE : + (void) cmd_delete(state, msgmap, MCMD_NONE, + (in_index == View) ? cmd_delete_view : cmd_delete_index); + break; + + + /*---------- Undelete message ----------*/ + case MC_UNDELETE : + (void) cmd_undelete(state, msgmap, MCMD_NONE); + update_titlebar_status(); + break; + + + /*---------- Reply to message ----------*/ + case MC_REPLY : + (void) cmd_reply(state, msgmap, MCMD_NONE); + break; + + + /*---------- Forward message ----------*/ + case MC_FORWARD : + (void) cmd_forward(state, msgmap, MCMD_NONE); + break; + + + /*---------- Quit pine ------------*/ + case MC_QUIT : + state->next_screen = quit_screen; + dprint((1,"MAIL_CMD: quit\n")); + break; + + + /*---------- Compose message ----------*/ + case MC_COMPOSE : + state->prev_screen = (in_index == View) ? mail_view_screen + : mail_index_screen; + compose_screen(state); + state->mangled_screen = 1; + if (state->next_screen) + a_changed = TRUE; + break; + + + /*---------- Alt Compose message ----------*/ + case MC_ROLE : + state->prev_screen = (in_index == View) ? mail_view_screen + : mail_index_screen; + role_compose(state); + if(state->next_screen) + a_changed = TRUE; + + break; + + + /*--------- Folders menu ------------*/ + case MC_FOLDERS : + state->start_in_context = 1; + + /*--------- Top of Folders list menu ------------*/ + case MC_COLLECTIONS : + state->next_screen = folder_screen; + dprint((2,"MAIL_CMD: going to folder/collection menu\n")); + break; + + + /*---------- Open specific new folder ----------*/ + case MC_GOTO : + tc = (state->context_last && !NEWS_TEST(state->context_current)) + ? state->context_last : state->context_current; + + newfolder = broach_folder(question_line, 1, ¬realinbox, &tc); + if(newfolder){ + visit_folder(state, newfolder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT); + a_changed = TRUE; + } + + break; + + + /*------- Go to Index Screen ----------*/ + case MC_INDEX : + state->next_screen = mail_index_screen; + break; + + /*------- Skip to next interesting message -----------*/ + case MC_TAB : + if(THRD_INDX()){ + PINETHRD_S *thrd; + + /* + * If we're in the thread index, start looking after this + * thread. We don't want to match something in the current + * thread. + */ + start = mn_get_cur(msgmap); + thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); + if(mn_get_revsort(msgmap)){ + /* if reversed, top of thread is last one before next thread */ + if(thrd && thrd->top) + start = mn_raw2m(msgmap, thrd->top); + } + else{ + /* last msg of thread is at the ends of the branches/nexts */ + while(thrd){ + start = mn_raw2m(msgmap, thrd->rawno); + if(thrd->branch) + thrd = fetch_thread(stream, thrd->branch); + else if(thrd->next) + thrd = fetch_thread(stream, thrd->next); + else + thrd = NULL; + } + } + + /* + * Flags is 0 in this case because we want to not skip + * messages inside of threads so that we can find threads + * which have some unseen messages even though the top-level + * of the thread is already seen. + * If new_msgno ends up being a message which is not visible + * because it isn't at the top-level, the current message # + * will be adjusted below in adjust_cur. + */ + flags = 0; + new_msgno = next_sorted_flagged((F_UNDEL + | F_UNSEEN + | ((F_ON(F_TAB_TO_NEW,state)) + ? 0 : F_OR_FLAG)), + stream, start, &flags); + } + else if(THREADING() && sp_viewing_a_thread(stream)){ + PINETHRD_S *thrd, *topthrd = NULL; + + start = mn_get_cur(msgmap); + + /* + * Things are especially complicated when we're viewing_a_thread + * from the thread index. First we have to check within the + * current thread for a new message. If none is found, then + * we search in the next threads and offer to continue in + * them. Then we offer to go to the next folder. + */ + flags = NSF_SKIP_CHID; + new_msgno = next_sorted_flagged((F_UNDEL + | F_UNSEEN + | ((F_ON(F_TAB_TO_NEW,state)) + ? 0 : F_OR_FLAG)), + stream, start, &flags); + /* + * If we found a match then we are done, that is another message + * in the current thread index. Otherwise, we have to look + * further. + */ + if(!(flags & NSF_FLAG_MATCH)){ + ret = 'n'; + while(1){ + + flags = 0; + new_msgno = next_sorted_flagged((F_UNDEL + | F_UNSEEN + | ((F_ON(F_TAB_TO_NEW, + state)) + ? 0 : F_OR_FLAG)), + stream, start, &flags); + /* + * If we got a match, new_msgno is a message in + * a different thread from the one we are viewing. + */ + if(flags & NSF_FLAG_MATCH){ + thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno)); + if(thrd && thrd->top) + topthrd = fetch_thread(stream, thrd->top); + + if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){ + static ESCKEY_S next_opt[] = { + {'y', 'y', "Y", N_("Yes")}, + {'n', 'n', "N", N_("No")}, + {TAB, 'n', "Tab", N_("NextNew")}, + {-1, 0, NULL, NULL} + }; + + if(in_index) + snprintf(prompt, sizeof(prompt), _("View thread number %s? "), + topthrd ? comatose(topthrd->thrdno) : "?"); + else + snprintf(prompt, sizeof(prompt), + _("View message in thread number %s? "), + topthrd ? comatose(topthrd->thrdno) : "?"); + + prompt[sizeof(prompt)-1] = '\0'; + + ret = radio_buttons(prompt, -FOOTER_ROWS(state), + next_opt, 'y', 'x', NO_HELP, + RB_NORM); + if(ret == 'x'){ + cmd_cancelled(NULL); + goto get_out; + } + } + else + ret = 'y'; + + if(ret == 'y'){ + if(unview_thread(state, stream, msgmap)){ + state->next_screen = mail_index_screen; + state->view_skipped_index = 0; + state->mangled_screen = 1; + } + + mn_set_cur(msgmap, new_msgno); + if(THRD_AUTO_VIEW()){ + + if(count_lflags_in_thread(stream, topthrd, + msgmap, MN_NONE) == 1){ + if(view_thread(state, stream, msgmap, 1)){ + if(current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + state->view_skipped_index = 1; + command = MC_VIEW_TEXT; + goto view_text; + } + } + } + + if(view_thread(state, stream, msgmap, 1) && current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + state->next_screen = SCREEN_FUN_NULL; + break; + } + else if(ret == 'n' && topthrd){ + /* + * skip to end of this thread and look starting + * in the next thread. + */ + if(mn_get_revsort(msgmap)){ + /* + * if reversed, top of thread is last one + * before next thread + */ + start = mn_raw2m(msgmap, topthrd->rawno); + } + else{ + /* + * last msg of thread is at the ends of + * the branches/nexts + */ + thrd = topthrd; + while(thrd){ + start = mn_raw2m(msgmap, thrd->rawno); + if(thrd->branch) + thrd = fetch_thread(stream, thrd->branch); + else if(thrd->next) + thrd = fetch_thread(stream, thrd->next); + else + thrd = NULL; + } + } + } + else if(ret == 'n') + break; + } + else + break; + } + } + } + else{ + + start = mn_get_cur(msgmap); + + /* + * If we are on a collapsed thread, start looking after the + * collapsed part, unless we are viewing the message. + */ + if(THREADING() && in_index != View){ + PINETHRD_S *thrd; + long rawno; + int collapsed; + + rawno = mn_m2raw(msgmap, start); + thrd = fetch_thread(stream, rawno); + collapsed = thrd && thrd->next + && get_lflag(stream, NULL, rawno, MN_COLL); + + if(collapsed){ + if(mn_get_revsort(msgmap)){ + if(thrd && thrd->top) + start = mn_raw2m(msgmap, thrd->top); + } + else{ + while(thrd){ + start = mn_raw2m(msgmap, thrd->rawno); + if(thrd->branch) + thrd = fetch_thread(stream, thrd->branch); + else if(thrd->next) + thrd = fetch_thread(stream, thrd->next); + else + thrd = NULL; + } + } + + } + } + + new_msgno = next_sorted_flagged((F_UNDEL + | F_UNSEEN + | ((F_ON(F_TAB_TO_NEW,state)) + ? 0 : F_OR_FLAG)), + stream, start, &flags); + } + + /* + * If there weren't any unread messages left, OR there + * aren't any messages at all, we may want to offer to + * go on to the next folder... + */ + if(flags & NSF_FLAG_MATCH){ + mn_set_cur(msgmap, new_msgno); + if(in_index != View) + adjust_cur_to_visible(stream, msgmap); + } + else{ + int in_inbox = sp_flagged(stream, SP_INBOX); + + if(state->context_current + && ((NEWS_TEST(state->context_current) + && context_isambig(state->cur_folder)) + || ((state->context_current->use & CNTXT_INCMNG) + && (in_inbox + || folder_index(state->cur_folder, + state->context_current, + FI_FOLDER) >= 0)))){ + char nextfolder[MAXPATH]; + MAILSTREAM *nextstream = NULL; + long recent_cnt; + int did_cancel = 0; + + strncpy(nextfolder, state->cur_folder, sizeof(nextfolder)); + nextfolder[sizeof(nextfolder)-1] = '\0'; + while(1){ + if(!(next_folder(&nextstream, nextfolder, sizeof(nextfolder), nextfolder, + state->context_current, &recent_cnt, + F_ON(F_TAB_NO_CONFIRM,state) + ? NULL : &did_cancel))){ + if(!in_inbox){ + static ESCKEY_S inbox_opt[] = { + {'y', 'y', "Y", N_("Yes")}, + {'n', 'n', "N", N_("No")}, + {TAB, 'z', "Tab", N_("To Inbox")}, + {-1, 0, NULL, NULL} + }; + + if(F_ON(F_RET_INBOX_NO_CONFIRM,state)) + ret = 'y'; + else{ + /* TRANSLATORS: this is a question, with some information followed + by Return to INBOX? */ + if(state->context_current->use&CNTXT_INCMNG) + snprintf(prompt, sizeof(prompt), _("No more incoming folders. Return to \"%s\"? "), state->inbox_name); + else + snprintf(prompt, sizeof(prompt), _("No more news groups. Return to \"%s\"? "), state->inbox_name); + + ret = radio_buttons(prompt, -FOOTER_ROWS(state), + inbox_opt, 'y', 'x', + NO_HELP, RB_NORM); + } + + /* + * 'z' is a synonym for 'y'. It is not 'y' + * so that it isn't displayed as a default + * action with square-brackets around it + * in the keymenu... + */ + if(ret == 'y' || ret == 'z'){ + visit_folder(state, state->inbox_name, + state->context_current, + NULL, DB_INBOXWOCNTXT); + a_changed = TRUE; + } + } + else if (did_cancel) + cmd_cancelled(NULL); + else{ + if(state->context_current->use&CNTXT_INCMNG) + q_status_message(SM_ORDER, 0, 2, _("No more incoming folders")); + else + q_status_message(SM_ORDER, 0, 2, _("No more news groups")); + } + + break; + } + + {char *front, type[80], cnt[80], fbuf[MAX_SCREEN_COLS/2+1]; + int rbspace, avail, need, take_back; + + /* + * View_next_ + * Incoming_folder_ or news_group_ or folder_ or group_ + * "foldername" + * _(13 recent) or _(some recent) or nothing + * ?_ + */ + front = "View next"; + strncpy(type, + (state->context_current->use & CNTXT_INCMNG) + ? "Incoming folder" : "news group", + sizeof(type)); + type[sizeof(type)-1] = '\0'; + snprintf(cnt, sizeof(cnt), " (%.*s %s)", sizeof(cnt)-20, + recent_cnt ? long2string(recent_cnt) : "some", + F_ON(F_TAB_USES_UNSEEN, ps_global) + ? "unseen" : "recent"); + cnt[sizeof(cnt)-1] = '\0'; + + /* + * Space reserved for radio_buttons call. + * If we make this 3 then radio_buttons won't mess + * with the prompt. If we make it 2, then we get + * one more character to use but radio_buttons will + * cut off the last character of our prompt, which is + * ok because it is a space. + */ + rbspace = 2; + avail = ps_global->ttyo ? ps_global->ttyo->screen_cols + : 80; + need = strlen(front)+1 + strlen(type)+1 + + + strlen(nextfolder)+2 + strlen(cnt) + + 2 + rbspace; + if(avail < need){ + take_back = strlen(type); + strncpy(type, + (state->context_current->use & CNTXT_INCMNG) + ? "folder" : "group", sizeof(type)); + take_back -= strlen(type); + need -= take_back; + if(avail < need){ + need -= strlen(cnt); + cnt[0] = '\0'; + } + } + + snprintf(prompt, sizeof(prompt), "%.*s %.*s \"%.*s\"%.*s? ", + sizeof(prompt)/8, front, + sizeof(prompt)/8, type, + sizeof(prompt)/2, + short_str(nextfolder, fbuf, sizeof(fbuf), + strlen(nextfolder) - + ((need>avail) ? (need-avail) : 0), + MidDots), + sizeof(prompt)/8, cnt); + prompt[sizeof(prompt)-1] = '\0'; + } + + /* + * When help gets added, this'll have to become + * a loop like the rest... + */ + if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){ + static ESCKEY_S next_opt[] = { + {'y', 'y', "Y", N_("Yes")}, + {'n', 'n', "N", N_("No")}, + {TAB, 'n', "Tab", N_("NextNew")}, + {-1, 0, NULL, NULL} + }; + + ret = radio_buttons(prompt, -FOOTER_ROWS(state), + next_opt, 'y', 'x', NO_HELP, + RB_NORM); + if(ret == 'x'){ + cmd_cancelled(NULL); + break; + } + } + else + ret = 'y'; + + if(ret == 'y'){ + if(nextstream && sp_dead_stream(nextstream)) + nextstream = NULL; + + visit_folder(state, nextfolder, + state->context_current, nextstream, + DB_FROMTAB); + /* visit_folder takes care of nextstream */ + nextstream = NULL; + a_changed = TRUE; + break; + } + } + + if(nextstream) + pine_mail_close(nextstream); + } + else + any_messages(NULL, + (mn_get_total(msgmap) > 0L) + ? IS_NEWS(stream) ? "more undeleted" : "more new" + : NULL, + NULL); + } + +get_out: + + break; + + + /*------- Zoom -----------*/ + case MC_ZOOM : + /* + * Right now the way zoom is implemented is sort of silly. + * There are two per-message flags where just one and a + * global "zoom mode" flag to suppress messags from the index + * should suffice. + */ + if(any_messages(msgmap, NULL, "to Zoom on")){ + if(unzoom_index(state, stream, msgmap)){ + dprint((4, "\n\n ---- Exiting ZOOM mode ----\n")); + q_status_message(SM_ORDER,0,2, _("Index Zoom Mode is now off")); + } + else if((i = zoom_index(state, stream, msgmap, MN_SLCT)) != 0){ + if(any_lflagged(msgmap, MN_HIDE)){ + dprint((4,"\n\n ---- Entering ZOOM mode ----\n")); + q_status_message4(SM_ORDER, 0, 2, + _("In Zoomed Index of %s%s%s%s. Use \"Z\" to restore regular Index"), + THRD_INDX() ? "" : comatose(i), + THRD_INDX() ? "" : " ", + THRD_INDX() ? _("threads") : _("message"), + THRD_INDX() ? "" : plural(i)); + } + else + q_status_message(SM_ORDER, 0, 2, + _("All messages selected, so not entering Index Zoom Mode")); + } + else + any_messages(NULL, "selected", "to Zoom on"); + } + + break; + + + /*---------- print message on paper ----------*/ + case MC_PRINTMSG : + if(any_messages(msgmap, NULL, "to print")) + (void) cmd_print(state, msgmap, MCMD_NONE, in_index); + + break; + + + /*---------- Take Address ----------*/ + case MC_TAKE : + if(F_ON(F_ENABLE_ROLE_TAKE, state) || + any_messages(msgmap, NULL, "to Take address from")) + (void) cmd_take_addr(state, msgmap, MCMD_NONE); + + break; + + + /*---------- Save Message ----------*/ + case MC_SAVE : + if(any_messages(msgmap, NULL, "to Save")) + (void) cmd_save(state, stream, msgmap, MCMD_NONE, in_index); + + break; + + + /*---------- Export message ----------*/ + case MC_EXPORT : + if(any_messages(msgmap, NULL, "to Export")){ + (void) cmd_export(state, msgmap, question_line, MCMD_NONE); + state->mangled_footer = 1; + } + + break; + + + /*---------- Expunge ----------*/ + case MC_EXPUNGE : + cmd_expunge(state, stream, msgmap); + break; + + + /*------- Unexclude -----------*/ + case MC_UNEXCLUDE : + if(!(IS_NEWS(stream) && stream->rdonly)){ + q_status_message(SM_ORDER, 0, 3, + _("Unexclude not available for mail folders")); + } + else if(any_lflagged(msgmap, MN_EXLD)){ + SEARCHPGM *pgm; + long i; + int exbits; + + /* + * Since excluded means "hidden deleted" and "killed", + * the count should reflect the former. + */ + pgm = mail_newsearchpgm(); + pgm->deleted = 1; + pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); + for(i = 1L, del_count = 0L; i <= stream->nmsgs; i++) + if((mc = mail_elt(stream, i)) && mc->searched + && get_lflag(stream, NULL, i, MN_EXLD) + && !(msgno_exceptions(stream, i, "0", &exbits, FALSE) + && (exbits & MSG_EX_FILTERED))) + del_count++; + + if(del_count > 0L){ + state->mangled_footer = 1; + snprintf(prompt, sizeof(prompt), "UNexclude %ld message%s in %.*s", del_count, + plural(del_count), sizeof(prompt)-40, + pretty_fn(state->cur_folder)); + prompt[sizeof(prompt)-1] = '\0'; + if(F_ON(F_FULL_AUTO_EXPUNGE, state) + || (F_ON(F_AUTO_EXPUNGE, state) + && (state->context_current + && (state->context_current->use & CNTXT_INCMNG)) + && context_isambig(state->cur_folder)) + || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){ + long save_cur_rawno; + int were_viewing_a_thread; + + save_cur_rawno = mn_m2raw(msgmap, mn_get_cur(msgmap)); + were_viewing_a_thread = (THREADING() + && sp_viewing_a_thread(stream)); + + if(msgno_include(stream, msgmap, MI_NONE)){ + clear_index_cache(stream, 0); + + if(stream && stream->spare) + erase_threading_info(stream, msgmap); + + refresh_sort(stream, msgmap, SRT_NON); + } + + if(were_viewing_a_thread){ + if(save_cur_rawno > 0L) + mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno)); + + if(view_thread(state, stream, msgmap, 1) && current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + } + + if(save_cur_rawno > 0L) + mn_set_cur(msgmap, mn_raw2m(msgmap,save_cur_rawno)); + + state->mangled_screen = 1; + q_status_message2(SM_ORDER, 0, 4, + "%s message%s UNexcluded", + long2string(del_count), + plural(del_count)); + + if(in_index != View) + adjust_cur_to_visible(stream, msgmap); + } + else + any_messages(NULL, NULL, "UNexcluded"); + } + else + any_messages(NULL, "excluded", "to UNexclude"); + } + else + any_messages(NULL, "excluded", "to UNexclude"); + + break; + + + /*------- Make Selection -----------*/ + case MC_SELECT : + if(any_messages(msgmap, NULL, "to Select")){ + if(aggregate_select(state, msgmap, question_line, in_index) == 0 + && (in_index == MsgIndx || in_index == ThrdIndx) + && F_ON(F_AUTO_ZOOM, state) + && any_lflagged(msgmap, MN_SLCT) > 0L + && !any_lflagged(msgmap, MN_HIDE)) + (void) zoom_index(state, stream, msgmap, MN_SLCT); + } + + break; + + + /*------- Toggle Current Message Selection State -----------*/ + case MC_SELCUR : + if(any_messages(msgmap, NULL, NULL)){ + if((select_by_current(state, msgmap, in_index) + || (F_OFF(F_UNSELECT_WONT_ADVANCE, state) + && !any_lflagged(msgmap, MN_HIDE))) + && (i = mn_get_cur(msgmap)) < mn_get_total(msgmap)){ + /* advance current */ + mn_inc_cur(stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + } + } + + break; + + + /*------- Apply command -----------*/ + case MC_APPLY : + if(any_messages(msgmap, NULL, NULL)){ + if(any_lflagged(msgmap, MN_SLCT) > 0L){ + if(apply_command(state, stream, msgmap, 0, + AC_NONE, question_line)){ + if(F_ON(F_AUTO_UNSELECT, state)){ + agg_select_all(stream, msgmap, NULL, 0); + unzoom_index(state, stream, msgmap); + } + else if(F_ON(F_AUTO_UNZOOM, state)) + unzoom_index(state, stream, msgmap); + } + } + else + any_messages(NULL, NULL, "to Apply command to. Try \"Select\""); + } + + break; + + + /*-------- Sort command -------*/ + case MC_SORT : + { + int were_threading = THREADING(); + SortOrder sort = mn_get_sort(msgmap); + int rev = mn_get_revsort(msgmap); + + dprint((1,"MAIL_CMD: sort\n")); + if(select_sort(state, question_line, &sort, &rev)){ + /* $ command reinitializes threading collapsed/expanded info */ + if(SORT_IS_THREADED(msgmap) && !SEP_THRDINDX()) + erase_threading_info(stream, msgmap); + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + sort_folder(stream, msgmap, sort, rev, SRT_VRB|SRT_MAN); + } + + state->mangled_footer = 1; + + /* + * We've changed whether we are threading or not so we need to + * exit the index and come back in so that we switch between the + * thread index and the regular index. Sort_folder will have + * reset viewing_a_thread if necessary. + */ + if(SEP_THRDINDX() + && ((!were_threading && THREADING()) + || (were_threading && !THREADING()))){ + state->next_screen = mail_index_screen; + state->mangled_screen = 1; + } + } + + break; + + + /*------- Toggle Full Headers -----------*/ + case MC_FULLHDR : + state->full_header++; + if(state->full_header == 1){ + if(!(state->quote_suppression_threshold + && (state->some_quoting_was_suppressed || in_index != View))) + state->full_header++; + } + else if(state->full_header > 2) + state->full_header = 0; + + switch(state->full_header){ + case 0: + q_status_message(SM_ORDER, 0, 3, + _("Display of full headers is now off.")); + break; + + case 1: + q_status_message1(SM_ORDER, 0, 3, + _("Quotes displayed, use %s to see full headers"), + F_ON(F_USE_FK, state) ? "F9" : "H"); + break; + + case 2: + q_status_message(SM_ORDER, 0, 3, + _("Display of full headers is now on.")); + break; + + } + + a_changed = TRUE; + break; + + + case MC_TOGGLE : + a_changed = TRUE; + break; + + +#ifdef SMIME + /*------- Try to decrypt message -----------*/ + case MC_DECRYPT: + if(state->smime && state->smime->need_passphrase) + smime_get_passphrase(); + + a_changed = TRUE; + break; + + case MC_SECURITY: + state->next_screen = smime_info_screen; + break; +#endif + + + /*------- Bounce -----------*/ + case MC_BOUNCE : + (void) cmd_bounce(state, msgmap, MCMD_NONE); + break; + + + /*------- Flag -----------*/ + case MC_FLAG : + dprint((4, "\n - flag message -\n")); + (void) cmd_flag(state, msgmap, MCMD_NONE); + break; + + + /*------- Pipe message -----------*/ + case MC_PIPE : + (void) cmd_pipe(state, msgmap, MCMD_NONE); + break; + + + /*--------- Default, unknown command ----------*/ + default: + panic("Unexpected command case"); + break; + } + + return((a_changed || mn_get_cur(msgmap) != old_msgno) ? 1 : 0); +} + + + +/*---------------------------------------------------------------------- + Map some of the special characters into sensible strings for human + consumption. + c is a UCS-4 character! + ----*/ +char * +pretty_command(UCS c) +{ + static char buf[10]; + char *s; + + buf[0] = '\0'; + s = buf; + + switch(c){ + case ' ' : s = "SPACE"; break; + case '\033' : s = "ESC"; break; + case '\177' : s = "DEL"; break; + case ctrl('I') : s = "TAB"; break; + case ctrl('J') : s = "LINEFEED"; break; + case ctrl('M') : s = "RETURN"; break; + case ctrl('Q') : s = "XON"; break; + case ctrl('S') : s = "XOFF"; break; + case KEY_UP : s = "Up Arrow"; break; + case KEY_DOWN : s = "Down Arrow"; break; + case KEY_RIGHT : s = "Right Arrow"; break; + case KEY_LEFT : s = "Left Arrow"; break; + case KEY_PGUP : s = "Prev Page"; break; + case KEY_PGDN : s = "Next Page"; break; + case KEY_HOME : s = "Home"; break; + case KEY_END : s = "End"; break; + case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */ + case KEY_JUNK : s = "Junk!"; break; + case BADESC : s = "Bad Esc"; break; + case NO_OP_IDLE : s = "NO_OP_IDLE"; break; + case NO_OP_COMMAND : s = "NO_OP_COMMAND"; break; + case KEY_RESIZE : s = "KEY_RESIZE"; break; + case KEY_UTF8 : s = "KEY_UTF8"; break; + case KEY_MOUSE : s = "KEY_MOUSE"; break; + case KEY_SCRLUPL : s = "KEY_SCRLUPL"; break; + case KEY_SCRLDNL : s = "KEY_SCRLDNL"; break; + case KEY_SCRLTO : s = "KEY_SCRLTO"; break; + case KEY_XTERM_MOUSE : s = "KEY_XTERM_MOUSE"; break; + case KEY_DOUBLE_ESC : s = "KEY_DOUBLE_ESC"; break; + case CTRL_KEY_UP : s = "Ctrl Up Arrow"; break; + case CTRL_KEY_DOWN : s = "Ctrl Down Arrow"; break; + case CTRL_KEY_RIGHT : s = "Ctrl Right Arrow"; break; + case CTRL_KEY_LEFT : s = "Ctrl Left Arrow"; break; + case PF1 : + case PF2 : + case PF3 : + case PF4 : + case PF5 : + case PF6 : + case PF7 : + case PF8 : + case PF9 : + case PF10 : + case PF11 : + case PF12 : + snprintf(s = buf, sizeof(buf), "F%ld", (long) (c - PF1 + 1)); + break; + + default: + if(c < ' ' || (c >= 0x80 && c < 0xA0)){ + char d; + int c1; + + c1 = (c >= 0x80); + d = (c & 0x1f) + 'A' - 1; + snprintf(s = buf, sizeof(buf), "%c%c", c1 ? '~' : '^', d); + } + else{ + memset(buf, 0, sizeof(buf)); + utf8_put((unsigned char *) buf, (unsigned long) c); + } + + break; + } + + return(s); +} + + +/*---------------------------------------------------------------------- + Complain about bogus input + + Args: ch -- input command to complain about + help -- string indicating where to get help + + ----*/ +void +bogus_command(UCS cmd, char *help) +{ + if(cmd == ctrl('Q') || cmd == ctrl('S')) + q_status_message1(SM_ASYNC, 0, 2, + "%s char received. Set \"preserve-start-stop\" feature in Setup/Config.", + pretty_command(cmd)); + else if(cmd == KEY_JUNK) + q_status_message3(SM_ORDER, 0, 2, + "Invalid key pressed.%s%s%s", + (help) ? " Use " : "", + (help) ? help : "", + (help) ? " for help" : ""); + else + q_status_message4(SM_ORDER, 0, 2, + "Command \"%s\" not defined for this screen.%s%s%s", + pretty_command(cmd), + (help) ? " Use " : "", + (help) ? help : "", + (help) ? " for help" : ""); +} + + +void +bogus_utf8_command(char *cmd, char *help) +{ + q_status_message4(SM_ORDER, 0, 2, + "Command \"%s\" not defined for this screen.%s%s%s", + cmd ? cmd : "?", + (help) ? " Use " : "", + (help) ? help : "", + (help) ? " for help" : ""); +} + + +/*---------------------------------------------------------------------- + Execute FLAG message command + + Args: state -- Various satate info + msgmap -- map of c-client to local message numbers + + Result: with side effect of "current" message FLAG flag set or UNset + + ----*/ +int +cmd_flag(struct pine *state, MSGNO_S *msgmap, int aopt) +{ + char *flagit, *seq, *screen_text[20], **exp, **p, *answer = NULL; + char *keyword_array[2]; + int user_defined_flags = 0, mailbox_flags = 0; + int directly_to_maint_screen = 0; + long unflagged, flagged, flags, rawno; + MESSAGECACHE *mc = NULL; + KEYWORD_S *kw; + int i, cnt, is_set, trouble = 0, rv = 0; + size_t len; + struct flag_table *fp, *ftbl = NULL; + struct flag_screen flag_screen; + static char *flag_screen_text1[] = { + N_(" Set desired flags for current message below. An 'X' means set"), + N_(" it, and a ' ' means to unset it. Choose \"Exit\" when finished."), + NULL + }; + + static char *flag_screen_text2[] = { + N_(" Set desired flags below for selected messages. A '?' means to"), + N_(" leave the flag unchanged, 'X' means to set it, and a ' ' means"), + N_(" to unset it. Use the \"Return\" key to toggle, and choose"), + N_(" \"Exit\" when finished."), + NULL + }; + + static struct flag_table default_ftbl[] = { + {N_("Important"), h_flag_important, F_FLAG, 0, 0, NULL, NULL}, + {N_("New"), h_flag_new, F_SEEN, 0, 0, NULL, NULL}, + {N_("Answered"), h_flag_answered, F_ANS, 0, 0, NULL, NULL}, + {N_("Forwarded"), h_flag_forwarded, F_FWD, 0, 0, NULL, NULL}, + {N_("Deleted"), h_flag_deleted, F_DEL, 0, 0, NULL, NULL}, + {NULL, NO_HELP, 0, 0, 0, NULL, NULL} + }; + + /* Only check for dead stream for now. Should check permanent flags + * eventually + */ + if(!(any_messages(msgmap, NULL, "to Flag") && can_set_flag(state, "flag", 1))) + return rv; + + if(sp_io_error_on_stream(state->mail_stream)){ + sp_set_io_error_on_stream(state->mail_stream, 0); + pine_mail_check(state->mail_stream); /* forces write */ + return rv; + } + +go_again: + answer = NULL; + user_defined_flags = 0; + mailbox_flags = 0; + mc = NULL; + trouble = 0; + ftbl = NULL; + + /* count how large ftbl will be */ + for(cnt = 0; default_ftbl[cnt].name; cnt++) + ; + + /* add user flags */ + for(kw = ps_global->keywords; kw; kw = kw->next){ + if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){ + user_defined_flags++; + cnt++; + } + } + + /* + * Add mailbox flags that aren't user-defined flags. + * Don't consider it if it matches either one of our defined + * keywords or one of our defined nicknames for a keyword. + */ + for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){ + char *q; + + q = stream_to_user_flag_name(state->mail_stream, i); + if(q && q[0]){ + for(kw = ps_global->keywords; kw; kw = kw->next){ + if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q))) + break; + } + } + + if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){ + mailbox_flags++; + cnt++; + } + } + + cnt += (user_defined_flags ? 2 : 0) + (mailbox_flags ? 2 : 0); + + /* set up ftbl, first the system flags */ + ftbl = (struct flag_table *) fs_get((cnt+1) * sizeof(*ftbl)); + memset(ftbl, 0, (cnt+1) * sizeof(*ftbl)); + for(i = 0, fp = ftbl; default_ftbl[i].name; i++, fp++){ + fp->name = cpystr(default_ftbl[i].name); + fp->help = default_ftbl[i].help; + fp->flag = default_ftbl[i].flag; + fp->set = default_ftbl[i].set; + fp->ukn = default_ftbl[i].ukn; + } + + if(user_defined_flags){ + fp->flag = F_COMMENT; + fp->name = cpystr(""); + fp++; + fp->flag = F_COMMENT; + len = strlen(_("User-defined Keywords from Setup/Config")); + fp->name = (char *) fs_get((len+6+6+1) * sizeof(char)); + snprintf(fp->name, len+6+6+1, "----- %s -----", _("User-defined Keywords from Setup/Config")); + fp++; + } + + /* then the user-defined keywords */ + if(user_defined_flags) + for(kw = ps_global->keywords; kw; kw = kw->next){ + if(!((kw->nick && !strucmp(FORWARDED_FLAG, kw->nick)) + || (kw->kw && !strucmp(FORWARDED_FLAG, kw->kw)))){ + fp->name = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : ""); + fp->keyword = cpystr(kw->kw ? kw->kw : ""); + if(kw->nick && kw->kw){ + size_t l; + + l = strlen(kw->kw)+2; + fp->comment = (char *) fs_get((l+1) * sizeof(char)); + snprintf(fp->comment, l+1, "(%.*s)", strlen(kw->kw), kw->kw); + fp->comment[l] = '\0'; + } + + fp->help = h_flag_user_flag; + fp->flag = F_KEYWORD; + fp->set = 0; + fp->ukn = 0; + fp++; + } + } + + if(mailbox_flags){ + fp->flag = F_COMMENT; + fp->name = cpystr(""); + fp++; + fp->flag = F_COMMENT; + len = strlen(_("Other keywords in the mailbox that are not user-defined")); + fp->name = (char *) fs_get((len+6+6+1) * sizeof(char)); + snprintf(fp->name, len+6+6+1, "----- %s -----", _("Other keywords in the mailbox that are not user-defined")); + fp++; + } + + /* then the extra mailbox-defined keywords */ + if(mailbox_flags) + for(i = 0; stream_to_user_flag_name(state->mail_stream, i); i++){ + char *q; + + q = stream_to_user_flag_name(state->mail_stream, i); + if(q && q[0]){ + for(kw = ps_global->keywords; kw; kw = kw->next){ + if((kw->nick && !strucmp(kw->nick, q)) || (kw->kw && !strucmp(kw->kw, q))) + break; + } + } + + if(!kw && !(q && !strucmp(FORWARDED_FLAG, q))){ + fp->name = cpystr(q); + fp->keyword = cpystr(q); + fp->help = h_flag_user_flag; + fp->flag = F_KEYWORD; + fp->set = 0; + fp->ukn = 0; + fp++; + } + } + + flag_screen.flag_table = &ftbl; + flag_screen.explanation = screen_text; + + if(MCMD_ISAGG(aopt)){ + if(!pseudo_selected(ps_global->mail_stream, msgmap)){ + free_flag_table(&ftbl); + return rv; + } + + exp = flag_screen_text2; + for(fp = ftbl; fp->name; fp++){ + fp->set = CMD_FLAG_UNKN; /* set to unknown */ + fp->ukn = TRUE; + } + } + else if(state->mail_stream + && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L + && rawno <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, rawno))){ + exp = flag_screen_text1; + for(fp = &ftbl[0]; fp->name; fp++){ + fp->ukn = 0; + if(fp->flag == F_KEYWORD){ + /* see if this keyword is defined for this message */ + fp->set = CMD_FLAG_CLEAR; + if(user_flag_is_set(state->mail_stream, + rawno, fp->keyword)) + fp->set = CMD_FLAG_SET; + } + else if(fp->flag == F_FWD){ + /* see if forwarded keyword is defined for this message */ + fp->set = CMD_FLAG_CLEAR; + if(user_flag_is_set(state->mail_stream, + rawno, FORWARDED_FLAG)) + fp->set = CMD_FLAG_SET; + } + else if(fp->flag != F_COMMENT) + fp->set = ((fp->flag == F_SEEN && !mc->seen) + || (fp->flag == F_DEL && mc->deleted) + || (fp->flag == F_FLAG && mc->flagged) + || (fp->flag == F_ANS && mc->answered)) + ? CMD_FLAG_SET : CMD_FLAG_CLEAR; + } + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Error accessing message data")); + free_flag_table(&ftbl); + return rv; + } + + if(directly_to_maint_screen) + goto the_maint_screen; + +#ifdef _WINDOWS + if (mswin_usedialog ()) { + if (!os_flagmsgdialog (&ftbl[0])){ + free_flag_table(&ftbl); + return rv; + } + } + else +#endif + { + int use_maint_screen; + int keyword_shortcut = 0; + + use_maint_screen = F_ON(F_FLAG_SCREEN_DFLT, ps_global); + + if(!use_maint_screen){ + /* + * We're going to call cmd_flag_prompt(). We need + * to decide whether or not to offer the keyword setting + * shortcut. We'll offer it if the user has the feature + * enabled AND there are some possible keywords that could + * be set. + */ + if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global)){ + for(fp = &ftbl[0]; fp->name && !keyword_shortcut; fp++){ + if(fp->flag == F_KEYWORD){ + int first_char; + ESCKEY_S *tp; + + first_char = (fp->name && fp->name[0]) + ? fp->name[0] : -2; + if(isascii(first_char) && isupper(first_char)) + first_char = tolower((unsigned char) first_char); + + for(tp=flag_text_opt; tp->ch != -1; tp++) + if(tp->ch == first_char) + break; + + if(tp->ch == -1) + keyword_shortcut++; + } + } + } + + use_maint_screen = !cmd_flag_prompt(state, &flag_screen, + keyword_shortcut); + } + +the_maint_screen: + if(use_maint_screen){ + for(p = &screen_text[0]; *exp; p++, exp++) + *p = *exp; + + *p = NULL; + + directly_to_maint_screen = flag_maintenance_screen(state, &flag_screen); + } + } + + /* reaquire the elt pointer */ + mc = (state->mail_stream + && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L + && rawno <= state->mail_stream->nmsgs) + ? mail_elt(state->mail_stream, rawno) : NULL; + + for(fp = ftbl; mc && fp->name; fp++){ + flags = -1; + switch(fp->flag){ + case F_SEEN: + if((!MCMD_ISAGG(aopt) && fp->set != !mc->seen) + || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ + flagit = "\\SEEN"; + if(fp->set){ + flags = 0L; + unflagged = F_SEEN; + } + else{ + flags = ST_SET; + unflagged = F_UNSEEN; + } + } + + break; + + case F_ANS: + if((!MCMD_ISAGG(aopt) && fp->set != mc->answered) + || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ + flagit = "\\ANSWERED"; + if(fp->set){ + flags = ST_SET; + unflagged = F_UNANS; + } + else{ + flags = 0L; + unflagged = F_ANS; + } + } + + break; + + case F_DEL: + if((!MCMD_ISAGG(aopt) && fp->set != mc->deleted) + || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ + flagit = "\\DELETED"; + if(fp->set){ + flags = ST_SET; + unflagged = F_UNDEL; + } + else{ + flags = 0L; + unflagged = F_DEL; + } + } + + break; + + case F_FLAG: + if((!MCMD_ISAGG(aopt) && fp->set != mc->flagged) + || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ + flagit = "\\FLAGGED"; + if(fp->set){ + flags = ST_SET; + unflagged = F_UNFLAG; + } + else{ + flags = 0L; + unflagged = F_FLAG; + } + } + + break; + + case F_FWD : + if(!MCMD_ISAGG(aopt)){ + /* see if forwarded is defined for this message */ + is_set = CMD_FLAG_CLEAR; + if(user_flag_is_set(state->mail_stream, + mn_m2raw(msgmap, mn_get_cur(msgmap)), + FORWARDED_FLAG)) + is_set = CMD_FLAG_SET; + } + + if((!MCMD_ISAGG(aopt) && fp->set != is_set) + || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ + flagit = FORWARDED_FLAG; + if(fp->set){ + flags = ST_SET; + unflagged = F_UNFWD; + } + else{ + flags = 0L; + unflagged = F_FWD; + } + } + + break; + + case F_KEYWORD: + if(!MCMD_ISAGG(aopt)){ + /* see if this keyword is defined for this message */ + is_set = CMD_FLAG_CLEAR; + if(user_flag_is_set(state->mail_stream, + mn_m2raw(msgmap, mn_get_cur(msgmap)), + fp->keyword)) + is_set = CMD_FLAG_SET; + } + + if((!MCMD_ISAGG(aopt) && fp->set != is_set) + || (MCMD_ISAGG(aopt) && fp->set != CMD_FLAG_UNKN)){ + flagit = fp->keyword; + keyword_array[0] = fp->keyword; + keyword_array[1] = NULL; + if(fp->set){ + flags = ST_SET; + unflagged = F_UNKEYWORD; + } + else{ + flags = 0L; + unflagged = F_KEYWORD; + } + } + + break; + + default: + break; + } + + flagged = 0L; + if(flags >= 0L + && (seq = currentf_sequence(state->mail_stream, msgmap, + unflagged, &flagged, unflagged & F_DEL, + (fp->flag == F_KEYWORD + && unflagged == F_KEYWORD) + ? keyword_array : NULL, + (fp->flag == F_KEYWORD + && unflagged == F_UNKEYWORD) + ? keyword_array : NULL))){ + /* + * For user keywords, we may have to create the flag in + * the folder if it doesn't already exist and we are setting + * it (as opposed to clearing it). Mail_flag will + * do that for us, but it's failure isn't very friendly + * error-wise. So we try to make it a little smoother. + */ + if(!(fp->flag == F_KEYWORD || fp->flag == F_FWD) || !fp->set + || ((i=user_flag_index(state->mail_stream, flagit)) >= 0 + && i < NUSERFLAGS)) + mail_flag(state->mail_stream, seq, flagit, flags); + else{ + /* trouble, see if we can add the user flag */ + if(state->mail_stream->kwd_create) + mail_flag(state->mail_stream, seq, flagit, flags); + else{ + trouble++; + + if(some_user_flags_defined(state->mail_stream)) + q_status_message(SM_ORDER, 3, 4, + _("No more keywords allowed in this folder!")); + else if(fp->flag == F_FWD) + q_status_message(SM_ORDER, 3, 4, + _("Cannot add keywords for this folder so cannot set Forwarded flag")); + else + q_status_message(SM_ORDER, 3, 4, + _("Cannot add keywords for this folder")); + } + } + + fs_give((void **) &seq); + if(flagged && !trouble){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%slagged%s%s%s%s%s message%s%s \"%s\"", + (fp->set) ? "F" : "Unf", + MCMD_ISAGG(aopt) ? " " : "", + MCMD_ISAGG(aopt) ? long2string(flagged) : "", + (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap)) + ? " (of " : "", + (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap)) + ? comatose(mn_total_cur(msgmap)) : "", + (MCMD_ISAGG(aopt) && flagged != mn_total_cur(msgmap)) + ? ")" : "", + MCMD_ISAGG(aopt) ? plural(flagged) : " ", + MCMD_ISAGG(aopt) ? "" : long2string(mn_get_cur(msgmap)), + fp->name); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + q_status_message(SM_ORDER, 0, 2, answer = tmp_20k_buf); + rv++; + } + } + } + + free_flag_table(&ftbl); + + if(directly_to_maint_screen) + goto go_again; + + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + if(!answer) + q_status_message(SM_ORDER, 0, 2, _("No flags changed.")); + + return rv; +} + + +/*---------------------------------------------------------------------- + Offer concise status line flag prompt + + Args: state -- Various satate info + flags -- flags to offer setting + + Result: TRUE if flag to set specified in flags struct or FALSE otw + + ----*/ +int +cmd_flag_prompt(struct pine *state, struct flag_screen *flags, int allow_keyword_shortcuts) +{ + int r, setflag = 1, first_char; + struct flag_table *fp; + ESCKEY_S *ek; + char *ftext, *ftext_not; + static char *flag_text = + N_("Flag New, Deleted, Answered, Forwarded or Important ? "); + static char *flag_text_ak = + N_("Flag New, Deleted, Answered, Forwarded, Important or Keyword initial ? "); + static char *flag_text_not = + N_("Flag !New, !Deleted, !Answered, !Forwarded, or !Important ? "); + static char *flag_text_ak_not = + N_("Flag !New, !Deleted, !Answered, !Forwarded, !Important or !Keyword initial ? "); + + if(allow_keyword_shortcuts){ + int cnt = 0; + ESCKEY_S *dp, *sp, *tp; + + for(sp=flag_text_opt; sp->ch != -1; sp++) + cnt++; + + for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++) + if(fp->flag == F_KEYWORD) + cnt++; + + /* set up an ESCKEY_S list which includes invisible keys for keywords */ + ek = (ESCKEY_S *) fs_get((cnt + 1) * sizeof(*ek)); + memset(ek, 0, (cnt+1) * sizeof(*ek)); + for(dp=ek, sp=flag_text_opt; sp->ch != -1; sp++, dp++) + *dp = *sp; + + for(fp=(flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){ + if(fp->flag == F_KEYWORD){ + first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2; + if(isascii(first_char) && isupper(first_char)) + first_char = tolower((unsigned char) first_char); + + /* + * Check to see if an earlier keyword in the list, or one of + * the builtin system letters already uses this character. + * If so, the first one wins. + */ + for(tp=ek; tp->ch != 0; tp++) + if(tp->ch == first_char) + break; + + if(tp->ch != 0) + continue; /* skip it, already used that char */ + + dp->ch = first_char; + dp->rval = first_char; + dp->name = ""; + dp->label = ""; + dp++; + } + } + + dp->ch = -1; + ftext = _(flag_text_ak); + ftext_not = _(flag_text_ak_not); + } + else{ + ek = flag_text_opt; + ftext = _(flag_text); + ftext_not = _(flag_text_not); + } + + while(1){ + r = radio_buttons(setflag ? ftext : ftext_not, + -FOOTER_ROWS(state), ek, '*', SEQ_EXCEPTION-1, + NO_HELP, RB_NORM | RB_SEQ_SENSITIVE); + /* + * It is SEQ_EXCEPTION-1 just so that it is some value that isn't + * being used otherwise. The keywords use up all the possible + * letters, so a negative number is good, but it has to be different + * from other negative return values. + */ + if(r == SEQ_EXCEPTION-1) /* ol'cancelrooney */ + return(TRUE); + else if(r == 10) /* return and goto flag screen */ + return(FALSE); + else if(r == '!') /* flip intention */ + setflag = !setflag; + else + break; + } + + for(fp = (flags->flag_table ? *flags->flag_table : NULL); fp->name; fp++){ + if(r == 'n' || r == '*' || r == 'd' || r == 'a' || r == 'f'){ + if((r == 'n' && fp->flag == F_SEEN) + || (r == '*' && fp->flag == F_FLAG) + || (r == 'd' && fp->flag == F_DEL) + || (r == 'f' && fp->flag == F_FWD) + || (r == 'a' && fp->flag == F_ANS)){ + fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR; + break; + } + } + else if(allow_keyword_shortcuts && fp->flag == F_KEYWORD){ + first_char = (fp->name && fp->name[0]) ? fp->name[0] : -2; + if(isascii(first_char) && isupper(first_char)) + first_char = tolower((unsigned char) first_char); + + if(r == first_char){ + fp->set = setflag ? CMD_FLAG_SET : CMD_FLAG_CLEAR; + break; + } + } + } + + if(ek != flag_text_opt) + fs_give((void **) &ek); + + return(TRUE); +} + + +/* + * (*ft) is an array of flag_table entries. + */ +void +free_flag_table(struct flag_table **ft) +{ + struct flag_table *fp; + + if(ft && *ft){ + for(fp = (*ft); fp->name; fp++){ + if(fp->name) + fs_give((void **) &fp->name); + + if(fp->keyword) + fs_give((void **) &fp->keyword); + + if(fp->comment) + fs_give((void **) &fp->comment); + } + + fs_give((void **) ft); + } +} + + +/*---------------------------------------------------------------------- + Execute REPLY message command + + Args: state -- Various satate info + msgmap -- map of c-client to local message numbers + + Result: reply sent or not + + ----*/ +int +cmd_reply(struct pine *state, MSGNO_S *msgmap, int aopt) +{ + int rv = 0; + + if(any_messages(msgmap, NULL, "to Reply to")){ + if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) + return rv; + + rv = reply(state, NULL); + + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + state->mangled_screen = 1; + } + + return rv; +} + + +/*---------------------------------------------------------------------- + Execute FORWARD message command + + Args: state -- Various satate info + msgmap -- map of c-client to local message numbers + + Result: selected message[s] forwarded or not + + ----*/ +int +cmd_forward(struct pine *state, MSGNO_S *msgmap, int aopt) +{ + int rv = 0; + + if(any_messages(msgmap, NULL, "to Forward")){ + if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) + return rv; + + rv = forward(state, NULL); + + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + state->mangled_screen = 1; + } + + return rv; +} + + +/*---------------------------------------------------------------------- + Execute BOUNCE message command + + Args: state -- Various satate info + msgmap -- map of c-client to local message numbers + aopt -- aggregate options + + Result: selected message[s] bounced or not + + ----*/ +int +cmd_bounce(struct pine *state, MSGNO_S *msgmap, int aopt) +{ + int rv = 0; + + if(any_messages(msgmap, NULL, "to Bounce")){ + if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) + return rv; + + rv = bounce(state, NULL); + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + state->mangled_footer = 1; + } + + return rv; +} + + +/*---------------------------------------------------------------------- + Execute save message command: prompt for folder and call function to save + + Args: screen_line -- Line on the screen to prompt on + message -- The MESSAGECACHE entry of message to save + + Result: The folder lister can be called to make selection; mangled screen set + + This does the prompting for the folder name to save to, possibly calling + up the folder display for selection of folder by user. + ----*/ +int +cmd_save(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, int aopt, CmdWhere in_index) +{ + char newfolder[MAILTMPLEN], nmsgs[32], *nick; + int we_cancel = 0, rv = 0, save_flags; + long i, raw; + CONTEXT_S *cntxt = NULL; + ENVELOPE *e = NULL; + SaveDel del = DontAsk; + SavePreserveOrder pre = DontAskPreserve; + + dprint((4, "\n - saving message -\n")); + + state->ugly_consider_advancing_bit = 0; + if(F_OFF(F_SAVE_PARTIAL_WO_CONFIRM, state) + && msgno_any_deletedparts(stream, msgmap) + && want_to(_("Saved copy will NOT include entire message! Continue"), + 'y', 'n', NO_HELP, WT_FLUSH_IN | WT_SEQ_SENSITIVE) != 'y'){ + cmd_cancelled("Save message"); + return rv; + } + + if(MCMD_ISAGG(aopt) && !pseudo_selected(stream, msgmap)) + return rv; + + raw = mn_m2raw(msgmap, mn_get_cur(msgmap)); + + if(mn_total_cur(msgmap) <= 1L){ + snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld ", mn_get_cur(msgmap)); + nmsgs[sizeof(nmsgs)-1] = '\0'; + e = pine_mail_fetchstructure(stream, raw, NULL); + if(!e) { + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Can't save message. Error accessing folder")); + restore_selected(msgmap); + return rv; + } + } + else{ + snprintf(nmsgs, sizeof(nmsgs), "%s msgs ", comatose(mn_total_cur(msgmap))); + nmsgs[sizeof(nmsgs)-1] = '\0'; + + /* e is just used to get a default save folder from the first msg */ + e = pine_mail_fetchstructure(stream, + mn_m2raw(msgmap, mn_first_cur(msgmap)), + NULL); + } + + del = (!READONLY_FOLDER(stream) && F_OFF(F_SAVE_WONT_DELETE, ps_global)) + ? Del : NoDel; + if(mn_total_cur(msgmap) > 1L) + pre = F_OFF(F_AGG_SEQ_COPY, ps_global) ? Preserve : NoPreserve; + else + pre = DontAskPreserve; + + if(save_prompt(state, &cntxt, newfolder, sizeof(newfolder), nmsgs, e, + raw, NULL, &del, &pre)){ + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + save_flags = SV_FIX_DELS; + if(pre == RetPreserve) + save_flags |= SV_PRESERVE; + if(del == RetDel) + save_flags |= SV_DELETE; + if(ps_global->context_list == cntxt && !strucmp(newfolder, ps_global->inbox_name)) + save_flags |= SV_INBOXWOCNTXT; + + we_cancel = busy_cue(_("Saving"), NULL, 1); + i = save(state, stream, cntxt, newfolder, msgmap, save_flags); + if(we_cancel) + cancel_busy_cue(0); + + if(i == mn_total_cur(msgmap)){ + rv++; + if(mn_total_cur(msgmap) <= 1L){ + int need, avail = ps_global->ttyo->screen_cols - 2; + int lennick, lenfldr; + + if(cntxt + && ps_global->context_list->next + && context_isambig(newfolder)){ + lennick = MIN(strlen(cntxt->nickname), 500); + lenfldr = MIN(strlen(newfolder), 500); + need = 27 + strlen(long2string(mn_get_cur(msgmap))) + + lenfldr + lennick; + if(need > avail){ + if(lennick > 10){ + need -= MIN(lennick-10, need-avail); + lennick -= MIN(lennick-10, need-avail); + } + + if(need > avail && lenfldr > 10) + lenfldr -= MIN(lenfldr-10, need-avail); + } + + snprintf(tmp_20k_buf, SIZEOF_20KBUF, + "Message %s copied to \"%s\" in <%s>", + long2string(mn_get_cur(msgmap)), + short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000, + lenfldr, MidDots), + short_str(cntxt->nickname, + (char *)(tmp_20k_buf+2000), 1000, + lennick, EndDots)); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + else if((nick=folder_is_target_of_nick(newfolder, cntxt)) != NULL){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, + "Message %s copied to \"%s\"", + long2string(mn_get_cur(msgmap)), + nick); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + else{ + char *f = " folder"; + + lenfldr = MIN(strlen(newfolder), 500); + need = 28 + strlen(long2string(mn_get_cur(msgmap))) + + lenfldr; + if(need > avail){ + need -= strlen(f); + f = ""; + if(need > avail && lenfldr > 10) + lenfldr -= MIN(lenfldr-10, need-avail); + } + + snprintf(tmp_20k_buf,SIZEOF_20KBUF, + "Message %s copied to%s \"%s\"", + long2string(mn_get_cur(msgmap)), f, + short_str(newfolder, (char *)(tmp_20k_buf+1000), 1000, + lenfldr, MidDots)); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + } + else{ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s messages saved", + comatose(mn_total_cur(msgmap))); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + + if(del == RetDel){ + strncat(tmp_20k_buf, " and deleted", SIZEOF_20KBUF-strlen(tmp_20k_buf)-1); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + } + + q_status_message(SM_ORDER, 0, 3, tmp_20k_buf); + + if(!MCMD_ISAGG(aopt) && F_ON(F_SAVE_ADVANCES, state)){ + if(sp_new_mail_count(stream)) + process_filter_patterns(stream, msgmap, + sp_new_mail_count(stream)); + + mn_inc_cur(stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + } + + state->ugly_consider_advancing_bit = 1; + } + } + + if(MCMD_ISAGG(aopt)) /* straighten out fakes */ + restore_selected(msgmap); + + if(del == RetDel) + update_titlebar_status(); /* make sure they see change */ + + return rv; +} + + +void +role_compose(struct pine *state) +{ + int action; + + if(F_ON(F_ALT_ROLE_MENU, state) && mn_get_total(state->msgmap) > 0L){ + PAT_STATE pstate; + + if(nonempty_patterns(ROLE_DO_ROLES, &pstate) && first_pattern(&pstate)){ + action = radio_buttons(_("Compose, Forward, Reply, or Bounce? "), + -FOOTER_ROWS(state), choose_action, + 'c', 'x', h_role_compose, RB_NORM); + } + else{ + q_status_message(SM_ORDER, 0, 3, + _("No roles available. Use Setup/Rules to add roles.")); + return; + } + } + else + action = 'c'; + + if(action == 'c' || action == 'r' || action == 'f' || action == 'b'){ + ACTION_S *role = NULL; + void (*prev_screen)(struct pine *) = NULL, (*redraw)(void) = NULL; + + redraw = state->redrawer; + state->redrawer = NULL; + prev_screen = state->prev_screen; + role = NULL; + state->next_screen = SCREEN_FUN_NULL; + + /* Setup role */ + if(role_select_screen(state, &role, + action == 'f' ? MC_FORWARD : + action == 'r' ? MC_REPLY : + action == 'b' ? MC_BOUNCE : + action == 'c' ? MC_COMPOSE : 0) < 0){ + cmd_cancelled(action == 'f' ? _("Forward") : + action == 'r' ? _("Reply") : + action == 'c' ? _("Composition") : _("Bounce")); + state->next_screen = prev_screen; + state->redrawer = redraw; + state->mangled_screen = 1; + } + else{ + /* + * If default role was selected (NULL) we need to make + * up a role which won't do anything, but will cause + * compose_mail to think there's already a role so that + * it won't try to confirm the default. + */ + if(role) + role = combine_inherited_role(role); + else{ + role = (ACTION_S *) fs_get(sizeof(*role)); + memset((void *) role, 0, sizeof(*role)); + role->nick = cpystr("Default Role"); + } + + state->redrawer = NULL; + switch(action){ + case 'c': + compose_mail(NULL, NULL, role, NULL, NULL); + break; + + case 'r': + (void) reply(state, role); + break; + + case 'f': + (void) forward(state, role); + break; + + case 'b': + (void) bounce(state, role); + break; + } + + if(role) + free_action(&role); + + state->next_screen = prev_screen; + state->redrawer = redraw; + state->mangled_screen = 1; + } + } +} + + +/*---------------------------------------------------------------------- + Do the dirty work of prompting the user for a folder name + + Args: + nfldr should be a buffer at least MAILTMPLEN long + dela -- a pointer to a SaveDel. If it is + DontAsk on input, don't offer Delete prompt + Del on input, offer Delete command with default of Delete + NoDel NoDelete + RetDel and RetNoDel are return values + + + Result: + + ----*/ +int +save_prompt(struct pine *state, CONTEXT_S **cntxt, char *nfldr, size_t len_nfldr, + char *nmsgs, ENVELOPE *env, long int rawmsgno, char *section, + SaveDel *dela, SavePreserveOrder *prea) +{ + int rc, ku = -1, n, flags, last_rc = 0, saveable_count = 0, done = 0; + int delindex, preindex, r; + char prompt[6*MAX_SCREEN_COLS+1], *p, expanded[MAILTMPLEN]; + char *buf = tmp_20k_buf; + char shortbuf[200]; + char *folder; + HelpType help; + SaveDel del = DontAsk; + SavePreserveOrder pre = DontAskPreserve; + char *deltext = NULL; + static HISTORY_S *history = NULL; + CONTEXT_S *tc; + ESCKEY_S ekey[10]; + + if(!cntxt) + panic("no context ptr in save_prompt"); + + init_hist(&history, HISTSIZE); + + if(!(folder = save_get_default(state, env, rawmsgno, section, cntxt))) + return(0); /* message expunged! */ + + /* how many context's can be saved to... */ + for(tc = state->context_list; tc; tc = tc->next) + if(!NEWS_TEST(tc)) + saveable_count++; + + /* set up extra command option keys */ + rc = 0; + ekey[rc].ch = ctrl('T'); + ekey[rc].rval = 2; + ekey[rc].name = "^T"; + /* TRANSLATORS: command means go to Folders list */ + ekey[rc++].label = N_("To Fldrs"); + + if(saveable_count > 1){ + ekey[rc].ch = ctrl('P'); + ekey[rc].rval = 10; + ekey[rc].name = "^P"; + ekey[rc++].label = N_("Prev Collection"); + + ekey[rc].ch = ctrl('N'); + ekey[rc].rval = 11; + ekey[rc].name = "^N"; + ekey[rc++].label = N_("Next Collection"); + } + + if(F_ON(F_ENABLE_TAB_COMPLETE, ps_global)){ + ekey[rc].ch = TAB; + ekey[rc].rval = 12; + ekey[rc].name = "TAB"; + /* TRANSLATORS: command asks alpine to complete the name when tab is typed */ + ekey[rc++].label = N_("Complete"); + } + + if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){ + ekey[rc].ch = ctrl('X'); + ekey[rc].rval = 14; + ekey[rc].name = "^X"; + /* TRANSLATORS: list all the matches */ + ekey[rc++].label = N_("ListMatches"); + } + + if(dela && (*dela == NoDel || *dela == Del)){ + ekey[rc].ch = ctrl('R'); + ekey[rc].rval = 15; + ekey[rc].name = "^R"; + delindex = rc++; + del = *dela; + } + + if(prea && (*prea == NoPreserve || *prea == Preserve)){ + ekey[rc].ch = ctrl('W'); + ekey[rc].rval = 16; + ekey[rc].name = "^W"; + preindex = rc++; + pre = *prea; + } + + if(saveable_count > 1 && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ + ekey[rc].ch = KEY_UP; + ekey[rc].rval = 10; + ekey[rc].name = ""; + ekey[rc++].label = ""; + + ekey[rc].ch = KEY_DOWN; + ekey[rc].rval = 11; + ekey[rc].name = ""; + ekey[rc++].label = ""; + } + else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ + ekey[rc].ch = KEY_UP; + ekey[rc].rval = 30; + ekey[rc].name = ""; + ku = rc; + ekey[rc++].label = ""; + + ekey[rc].ch = KEY_DOWN; + ekey[rc].rval = 31; + ekey[rc].name = ""; + ekey[rc++].label = ""; + } + + ekey[rc].ch = -1; + + *nfldr = '\0'; + help = NO_HELP; + while(!done){ + /* only show collection number if more than one available */ + if(ps_global->context_list->next) + snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder in <%s> [%s] : ", + deltext ? deltext : "", + nmsgs, + short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots), + strsquish(buf, SIZEOF_20KBUF, folder, 25)); + else + snprintf(prompt, sizeof(prompt), "SAVE%s %sto folder [%s] : ", + deltext ? deltext : "", + nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40)); + + prompt[sizeof(prompt)-1] = '\0'; + + /* + * If the prompt won't fit, try removing deltext. + */ + if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && deltext){ + if(ps_global->context_list->next) + snprintf(prompt, sizeof(prompt), "SAVE %sto folder in <%s> [%s] : ", + nmsgs, + short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots), + strsquish(buf, SIZEOF_20KBUF, folder, 25)); + else + snprintf(prompt, sizeof(prompt), "SAVE %sto folder [%s] : ", + nmsgs, strsquish(buf, SIZEOF_20KBUF, folder, 40)); + + prompt[sizeof(prompt)-1] = '\0'; + } + + /* + * If the prompt still won't fit, remove the extra info contained + * in nmsgs. + */ + if(state->ttyo->screen_cols < strlen(prompt) + MIN_OPT_ENT_WIDTH && *nmsgs){ + if(ps_global->context_list->next) + snprintf(prompt, sizeof(prompt), "SAVE to folder in <%s> [%s] : ", + short_str((*cntxt)->nickname, shortbuf, sizeof(shortbuf), 16, EndDots), + strsquish(buf, SIZEOF_20KBUF, folder, 25)); + else + snprintf(prompt, sizeof(prompt), "SAVE to folder [%s] : ", + strsquish(buf, SIZEOF_20KBUF, folder, 25)); + + prompt[sizeof(prompt)-1] = '\0'; + } + + if(del != DontAsk) + ekey[delindex].label = (del == NoDel) ? "Delete" : "No Delete"; + + if(pre != DontAskPreserve) + ekey[preindex].label = (pre == NoPreserve) ? "Preserve Order" : "Any Order"; + + if(ku >= 0){ + if(items_in_hist(history) > 1){ + ekey[ku].name = HISTORY_UP_KEYNAME; + ekey[ku].label = HISTORY_KEYLABEL; + ekey[ku+1].name = HISTORY_DOWN_KEYNAME; + ekey[ku+1].label = HISTORY_KEYLABEL; + } + else{ + ekey[ku].name = ""; + ekey[ku].label = ""; + ekey[ku+1].name = ""; + ekey[ku+1].label = ""; + } + } + + flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE; + rc = optionally_enter(nfldr, -FOOTER_ROWS(state), 0, len_nfldr, + prompt, ekey, help, &flags); + + switch(rc){ + case -1 : + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("Error reading folder name")); + done--; + break; + + case 0 : + removing_trailing_white_space(nfldr); + removing_leading_white_space(nfldr); + + if(*nfldr || *folder){ + char *p, *name, *fullname = NULL; + int exists, breakout = FALSE; + + if(!*nfldr){ + strncpy(nfldr, folder, len_nfldr-1); + nfldr[len_nfldr-1] = '\0'; + } + + save_hist(history, nfldr, 0, (void *) *cntxt); + + if(!(name = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))) + name = nfldr; + + if(update_folder_spec(expanded, sizeof(expanded), name)){ + strncpy(name = nfldr, expanded, len_nfldr-1); + nfldr[len_nfldr-1] = '\0'; + } + + exists = folder_name_exists(*cntxt, name, &fullname); + + if(exists == FEX_ERROR){ + q_status_message1(SM_ORDER, 0, 3, + _("Problem accessing folder \"%s\""), + nfldr); + done--; + } + else{ + if(fullname){ + strncpy(name = nfldr, fullname, len_nfldr-1); + nfldr[len_nfldr-1] = '\0'; + fs_give((void **) &fullname); + breakout = TRUE; + } + + if(exists & FEX_ISFILE){ + done++; + } + else if((exists & FEX_ISDIR)){ + char tmp[MAILTMPLEN]; + + tc = *cntxt; + if(breakout){ + CONTEXT_S *fake_context; + size_t l; + + strncpy(tmp, name, sizeof(tmp)); + tmp[sizeof(tmp)-2-1] = '\0'; + if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){ + if(l < sizeof(tmp)){ + tmp[l] = tc->dir->delim; + strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1)); + } + } + else + strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1); + + tmp[sizeof(tmp)-1] = '\0'; + + fake_context = new_context(tmp, 0); + nfldr[0] = '\0'; + done = display_folder_list(&fake_context, nfldr, + 1, folders_for_save); + free_context(&fake_context); + } + else if(tc->dir->delim + && (p = strrindex(name, tc->dir->delim)) + && *(p+1) == '\0') + done = display_folder_list(cntxt, nfldr, + 1, folders_for_save); + else{ + q_status_message1(SM_ORDER, 3, 3, + _("\"%s\" is a directory"), name); + if(tc->dir->delim + && !((p=strrindex(name, tc->dir->delim)) && *(p+1) == '\0')){ + strncpy(tmp, name, sizeof(tmp)); + tmp[sizeof(tmp)-1] = '\0'; + snprintf(nfldr, len_nfldr, "%s%c", tmp, tc->dir->delim); + } + } + } + else{ /* Doesn't exist, create! */ + if((fullname = folder_as_breakout(*cntxt, name)) != NULL){ + strncpy(name = nfldr, fullname, len_nfldr-1); + nfldr[len_nfldr-1] = '\0'; + fs_give((void **) &fullname); + } + + switch(create_for_save(*cntxt, name)){ + case 1 : /* success */ + done++; + break; + case 0 : /* error */ + case -1 : /* declined */ + done--; + break; + } + } + } + + break; + } + /* else fall thru like they cancelled */ + + case 1 : + cmd_cancelled("Save message"); + done--; + break; + + case 2 : + r = display_folder_list(cntxt, nfldr, 0, folders_for_save); + + if(r) + done++; + + break; + + case 3 : + helper(h_save, _("HELP FOR SAVE"), HLPD_SIMPLE); + ps_global->mangled_screen = 1; + break; + + case 4 : /* redraw */ + break; + + case 10 : /* previous collection */ + for(tc = (*cntxt)->prev; tc; tc = tc->prev) + if(!NEWS_TEST(tc)) + break; + + if(!tc){ + CONTEXT_S *tc2; + + for(tc2 = (tc = (*cntxt))->next; tc2; tc2 = tc2->next) + if(!NEWS_TEST(tc2)) + tc = tc2; + } + + *cntxt = tc; + break; + + case 11 : /* next collection */ + tc = (*cntxt); + + do + if(((*cntxt) = (*cntxt)->next) == NULL) + (*cntxt) = ps_global->context_list; + while(NEWS_TEST(*cntxt) && (*cntxt) != tc); + break; + + case 12 : /* file name completion */ + if(!folder_complete(*cntxt, nfldr, len_nfldr, &n)){ + if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){ + r = display_folder_list(cntxt, nfldr, 1, folders_for_save); + if(r) + done++; /* bingo! */ + else + rc = 0; /* burn last_rc */ + } + else + Writechar(BELL, 0); + } + + break; + + case 14 : /* file name completion */ + r = display_folder_list(cntxt, nfldr, 2, folders_for_save); + if(r) + done++; /* bingo! */ + else + rc = 0; /* burn last_rc */ + + break; + + case 15 : /* Delete / No Delete */ + del = (del == NoDel) ? Del : NoDel; + deltext = (del == NoDel) ? " (no delete)" : " (and delete)"; + break; + + case 16 : /* Preserve Order or not */ + pre = (pre == NoPreserve) ? Preserve : NoPreserve; + break; + + case 30 : + if((p = get_prev_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){ + strncpy(nfldr, p, len_nfldr); + nfldr[len_nfldr-1] = '\0'; + if(history->hist[history->curindex]) + *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt; + } + else + Writechar(BELL, 0); + + break; + + case 31 : + if((p = get_next_hist(history, nfldr, 0, (void *) *cntxt)) != NULL){ + strncpy(nfldr, p, len_nfldr); + nfldr[len_nfldr-1] = '\0'; + if(history->hist[history->curindex]) + *cntxt = (CONTEXT_S *) history->hist[history->curindex]->cntxt; + } + else + Writechar(BELL, 0); + + break; + + default : + panic("Unhandled case"); + break; + } + + last_rc = rc; + } + + ps_global->mangled_footer = 1; + + if(done < 0) + return(0); + + if(*nfldr){ + strncpy(ps_global->last_save_folder, nfldr, sizeof(ps_global->last_save_folder)-1); + ps_global->last_save_folder[sizeof(ps_global->last_save_folder)-1] = '\0'; + if(*cntxt) + ps_global->last_save_context = *cntxt; + } + else{ + strncpy(nfldr, folder, len_nfldr-1); + nfldr[len_nfldr-1] = '\0'; + } + + /* nickname? Copy real name to nfldr */ + if(*cntxt + && context_isambig(nfldr) + && (p = folder_is_nick(nfldr, FOLDERS(*cntxt), 0))){ + strncpy(nfldr, p, len_nfldr-1); + nfldr[len_nfldr-1] = '\0'; + } + + if(dela && (*dela == NoDel || *dela == Del)) + *dela = (del == NoDel) ? RetNoDel : RetDel; + + if(prea && (*prea == NoPreserve || *prea == Preserve)) + *prea = (pre == NoPreserve) ? RetNoPreserve : RetPreserve; + + return(1); +} + + +/*---------------------------------------------------------------------- + Prompt user before implicitly creating a folder for saving + + Args: context - context to create folder in + folder - folder name to create + + Result: 1 on proceed, -1 on decline, 0 on error + + ----*/ +int +create_for_save_prompt(CONTEXT_S *context, char *folder, int sequence_sensitive) +{ + if(context && ps_global->context_list->next && context_isambig(folder)){ + if(context->use & CNTXT_INCMNG){ + snprintf(tmp_20k_buf,SIZEOF_20KBUF, + _("\"%.15s%s\" doesn't exist - Add it in FOLDER LIST screen"), + folder, (strlen(folder) > 15) ? "..." : ""); + q_status_message(SM_ORDER, 3, 3, tmp_20k_buf); + return(0); /* error */ + } + + snprintf(tmp_20k_buf,SIZEOF_20KBUF, + _("Folder \"%.15s%s\" in <%.15s%s> doesn't exist. Create"), + folder, (strlen(folder) > 15) ? "..." : "", + context->nickname, + (strlen(context->nickname) > 15) ? "..." : ""); + } + else + snprintf(tmp_20k_buf, SIZEOF_20KBUF, + _("Folder \"%.40s%s\" doesn't exist. Create"), + folder, strlen(folder) > 40 ? "..." : ""); + + if(want_to(tmp_20k_buf, 'y', 'n', + NO_HELP, (sequence_sensitive) ? WT_SEQ_SENSITIVE : WT_NORM) != 'y'){ + cmd_cancelled("Save message"); + return(-1); + } + + return(1); +} + + + +/*---------------------------------------------------------------------- + Expunge messages from current folder + + Args: state -- pointer to struct holding a bunch of pine state + msgmap -- table mapping msg nums to c-client sequence nums + qline -- screen line to ask questions on + agg -- boolean indicating we're to operate on aggregate set + + Result: + ----*/ +void +cmd_expunge(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap) +{ + long del_count, prefilter_del_count; + int we_cancel = 0; + char prompt[MAX_SCREEN_COLS+1]; + COLOR_PAIR *lastc = NULL; + + dprint((2, "\n - expunge -\n")); + + if(IS_NEWS(stream) && stream->rdonly){ + if((del_count = count_flagged(stream, F_DEL)) > 0L){ + state->mangled_footer = 1; + snprintf(prompt, sizeof(prompt), "Exclude %ld message%s from %.*s", del_count, + plural(del_count), sizeof(prompt)-40, + pretty_fn(state->cur_folder)); + prompt[sizeof(prompt)-1] = '\0'; + if(F_ON(F_FULL_AUTO_EXPUNGE, state) + || (F_ON(F_AUTO_EXPUNGE, state) + && (state->context_current + && (state->context_current->use & CNTXT_INCMNG)) + && context_isambig(state->cur_folder)) + || want_to(prompt, 'y', 0, NO_HELP, WT_NORM) == 'y'){ + + if(F_ON(F_NEWS_CROSS_DELETE, state)) + cross_delete_crossposts(stream); + + msgno_exclude_deleted(stream, msgmap); + clear_index_cache(stream, 0); + + /* + * This is kind of surprising at first. For most sort + * orders, if the whole set is sorted, then any subset + * is also sorted. Not so for threaded sorts. + */ + if(SORT_IS_THREADED(msgmap)) + refresh_sort(stream, msgmap, SRT_NON); + + state->mangled_body = 1; + state->mangled_header = 1; + q_status_message2(SM_ORDER, 0, 4, + "%s message%s excluded", + long2string(del_count), + plural(del_count)); + } + else + any_messages(NULL, NULL, "Excluded"); + } + else + any_messages(NULL, "deleted", "to Exclude"); + + return; + } + else if(READONLY_FOLDER(stream)){ + q_status_message(SM_ORDER, 0, 4, + _("Can't expunge. Folder is read-only")); + return; + } + + prefilter_del_count = count_flagged(stream, F_DEL|F_NOFILT); + + mail_expunge_prefilter(stream, MI_NONE); + + if((del_count = count_flagged(stream, F_DEL|F_NOFILT)) != 0){ + int ret; + + snprintf(prompt, sizeof(prompt), "Expunge %ld message%s from %.*s", del_count, + plural(del_count), sizeof(prompt)-40, + pretty_fn(state->cur_folder)); + prompt[sizeof(prompt)-1] = '\0'; + state->mangled_footer = 1; + + if(F_ON(F_FULL_AUTO_EXPUNGE, state) + || (F_ON(F_AUTO_EXPUNGE, state) + && ((!strucmp(state->cur_folder,state->inbox_name)) + || (state->context_current->use & CNTXT_INCMNG)) + && context_isambig(state->cur_folder)) + || (ret=want_to(prompt, 'y', 0, NO_HELP, WT_NORM)) == 'y') + ret = 'y'; + + if(ret == 'x') + cmd_cancelled("Expunge"); + + if(ret != 'y') + return; + } + + dprint((8, "Expunge max:%ld cur:%ld kill:%d\n", + mn_get_total(msgmap), mn_get_cur(msgmap), del_count)); + + lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR, + state->VAR_TITLE_BACK_COLOR, + PSC_REV|PSC_RET); + + PutLine0(0, 0, "**"); /* indicate delay */ + + if(lastc){ + (void)pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + + MoveCursor(state->ttyo->screen_rows -FOOTER_ROWS(state), 0); + fflush(stdout); + + we_cancel = busy_cue(_("Expunging"), NULL, 1); + + if(cmd_expunge_work(stream, msgmap)) + state->mangled_body = 1; + + if(we_cancel) + cancel_busy_cue((sp_expunge_count(stream) > 0) ? 0 : -1); + + lastc = pico_set_colors(state->VAR_TITLE_FORE_COLOR, + state->VAR_TITLE_BACK_COLOR, + PSC_REV|PSC_RET); + PutLine0(0, 0, " "); /* indicate delay's over */ + + if(lastc){ + (void)pico_set_colorp(lastc, PSC_NONE); + free_color_pair(&lastc); + } + + fflush(stdout); + + if(sp_expunge_count(stream) > 0){ + /* + * This is kind of surprising at first. For most sort + * orders, if the whole set is sorted, then any subset + * is also sorted. Not so for threaded sorts. + */ + if(SORT_IS_THREADED(msgmap)) + refresh_sort(stream, msgmap, SRT_NON); + } + else{ + if(del_count) + q_status_message1(SM_ORDER, 0, 3, + _("No messages expunged from folder \"%s\""), + pretty_fn(state->cur_folder)); + else if(!prefilter_del_count) + q_status_message(SM_ORDER, 0, 3, + _("No messages marked deleted. No messages expunged.")); + } +} + + +/*---------------------------------------------------------------------- + Expunge_and_close callback to prompt user for confirmation + + Args: stream -- folder's stream + folder -- name of folder containing folders + deleted -- number of del'd msgs + + Result: 'y' to continue with expunge + ----*/ +int +expunge_prompt(MAILSTREAM *stream, char *folder, long int deleted) +{ + long max_folder; + int charcnt = 0; + char prompt_b[MAX_SCREEN_COLS+1], temp[MAILTMPLEN+1], buff[MAX_SCREEN_COLS+1]; + char *short_folder_name; + + if(deleted == 1) + charcnt = 1; + else{ + snprintf(temp, sizeof(temp), "%ld", deleted); + charcnt = strlen(temp)+1; + } + + max_folder = MAX(1,MAXPROMPT - (36+charcnt)); + strncpy(temp, folder, sizeof(temp)); + temp[sizeof(temp)-1] = '\0'; + short_folder_name = short_str(temp,buff,sizeof(buff),max_folder,FrontDots); + + if(IS_NEWS(stream)) + snprintf(prompt_b, sizeof(prompt_b), + "Delete %s%ld message%s from \"%s\"", + (deleted > 1L) ? "all " : "", deleted, + plural(deleted), short_folder_name); + else + snprintf(prompt_b, sizeof(prompt_b), + "Expunge the %ld deleted message%s from \"%s\"", + deleted, deleted == 1 ? "" : "s", + short_folder_name); + + return(want_to(prompt_b, 'y', 0, NO_HELP, WT_NORM)); +} + + +/* + * This is used with multiple append saves. Call it once before + * the series of appends with SSCP_INIT and once after all are + * done with SSCP_END. In between, it is called automatically + * from save_fetch_append or save_fetch_append_cb when we need + * to ask the user if he or she wants to continue even though + * announced message size doesn't match the actual message size. + * As of 2008-02-29 the gmail IMAP server has these size mismatches + * on a regular basis even though the data is ok. + */ +int +save_size_changed_prompt(long msgno, int flags) +{ + int ret; + char prompt[100]; + static int remember_the_yes = 0; + static int possible_corruption = 0; + static ESCKEY_S save_size_opts[] = { + {'y', 'y', "Y", "Yes"}, + {'n', 'n', "N", "No"}, + {'a', 'a', "A", "yes to All"}, + {-1, 0, NULL, NULL} + }; + + if(flags & SSCP_INIT || flags & SSCP_END){ + if(flags & SSCP_END && possible_corruption) + q_status_message(SM_ORDER, 3, 3, "There is possible data corruption, check the results"); + + remember_the_yes = 0; + possible_corruption = 0; + ps_global->noshow_error = 0; + ps_global->noshow_warn = 0; + return(0); + } + + if(remember_the_yes){ + snprintf(prompt, sizeof(prompt), + "Message to save shrank! (msg # %ld): Continuing", msgno); + q_status_message(SM_ORDER, 0, 3, prompt); + display_message('x'); + return(remember_the_yes); + } + + snprintf(prompt, sizeof(prompt), + "Message to save shrank! (msg # %ld): Continue anyway ? ", msgno); + ret = radio_buttons(prompt, -FOOTER_ROWS(ps_global), save_size_opts, + 'n', 0, h_save_size_changed, RB_NORM); + + switch(ret){ + case 'a': + remember_the_yes = 'y'; + possible_corruption++; + return(remember_the_yes); + + case 'y': + possible_corruption++; + return('y'); + + default: + possible_corruption = 0; + ps_global->noshow_error = 1; + ps_global->noshow_warn = 1; + break; + } + + return('n'); +} + + +/*---------------------------------------------------------------------- + Expunge_and_close callback that happens once the decision to expunge + and close has been made and before expunging and closing begins + + + Args: stream -- folder's stream + folder -- name of folder containing folders + deleted -- number of del'd msgs + + Result: 'y' to continue with expunge + ----*/ +void +expunge_and_close_begins(int flags, char *folder) +{ + if(!(flags & EC_NO_CLOSE)){ + q_status_message1(SM_INFO, 0, 1, "Closing \"%.200s\"...", folder); + flush_status_messages(1); + } +} + + +/*---------------------------------------------------------------------- + Export a message to a plain file in users home directory + + Args: state -- pointer to struct holding a bunch of pine state + msgmap -- table mapping msg nums to c-client sequence nums + qline -- screen line to ask questions on + agg -- boolean indicating we're to operate on aggregate set + + Result: + ----*/ +int +cmd_export(struct pine *state, MSGNO_S *msgmap, int qline, int aopt) +{ + char filename[MAXPATH+1], full_filename[MAXPATH+1], *err; + char nmsgs[80]; + int r, leading_nl, failure = 0, orig_errno, rflags = GER_NONE; + int flags = GE_IS_EXPORT | GE_SEQ_SENSITIVE, rv = 0; + ENVELOPE *env; + MESSAGECACHE *mc; + BODY *b; + long i, count = 0L, start_of_append, rawno; + gf_io_t pc; + STORE_S *store; + struct variable *vars = ps_global->vars; + ESCKEY_S export_opts[5]; + static HISTORY_S *history = NULL; + + if(ps_global->restricted){ + q_status_message(SM_ORDER, 0, 3, + "Alpine demo can't export messages to files"); + return rv; + } + + if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) + return rv; + + export_opts[i = 0].ch = ctrl('T'); + export_opts[i].rval = 10; + export_opts[i].name = "^T"; + export_opts[i++].label = N_("To Files"); + +#if !defined(DOS) && !defined(MAC) && !defined(OS2) + if(ps_global->VAR_DOWNLOAD_CMD && ps_global->VAR_DOWNLOAD_CMD[0]){ + export_opts[i].ch = ctrl('V'); + export_opts[i].rval = 12; + export_opts[i].name = "^V"; + /* TRANSLATORS: this is an abbreviation for Download Messages */ + export_opts[i++].label = N_("Downld Msg"); + } +#endif /* !(DOS || MAC) */ + + if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ + export_opts[i].ch = ctrl('I'); + export_opts[i].rval = 11; + export_opts[i].name = "TAB"; + export_opts[i++].label = N_("Complete"); + } + +#if 0 + /* Commented out since it's not yet support! */ + if(F_ON(F_ENABLE_SUB_LISTS,ps_global)){ + export_opts[i].ch = ctrl('X'); + export_opts[i].rval = 14; + export_opts[i].name = "^X"; + export_opts[i++].label = N_("ListMatches"); + } +#endif + + /* + * If message has attachments, add a toggle that will allow the user + * to save all of the attachments to a single directory, using the + * names provided with the attachments or part names. What we'll do is + * export the message as usual, and then export the attachments into + * a subdirectory that did not exist before. The subdir will be named + * something based on the name of the file being saved to, but a + * unique, new name. + */ + if(!MCMD_ISAGG(aopt) + && state->mail_stream + && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L + && rawno <= state->mail_stream->nmsgs + && (env = pine_mail_fetchstructure(state->mail_stream, rawno, &b)) + && b + && b->type == TYPEMULTIPART + && b->subtype + && strucmp(b->subtype, "ALTERNATIVE") != 0){ + PART *part; + + part = b->nested.part; /* 1st part */ + if(part && part->next) + flags |= GE_ALLPARTS; + } + + export_opts[i].ch = -1; + filename[0] = '\0'; + + if(mn_total_cur(msgmap) <= 1L){ + snprintf(nmsgs, sizeof(nmsgs), "Msg #%ld", mn_get_cur(msgmap)); + nmsgs[sizeof(nmsgs)-1] = '\0'; + } + else{ + snprintf(nmsgs, sizeof(nmsgs), "%s messages", comatose(mn_total_cur(msgmap))); + nmsgs[sizeof(nmsgs)-1] = '\0'; + } + + r = get_export_filename(state, filename, NULL, full_filename, + sizeof(filename), nmsgs, "EXPORT", + export_opts, &rflags, qline, flags, &history); + + if(r < 0){ + switch(r){ + case -1: + cmd_cancelled("Export message"); + break; + + case -2: + q_status_message1(SM_ORDER, 0, 2, + _("Can't export to file outside of %s"), + VAR_OPER_DIR); + break; + } + + goto fini; + } +#if !defined(DOS) && !defined(MAC) && !defined(OS2) + else if(r == 12){ /* Download */ + char cmd[MAXPATH], *tfp = NULL; + int next = 0; + PIPE_S *syspipe; + STORE_S *so; + gf_io_t pc; + + if(ps_global->restricted){ + q_status_message(SM_ORDER | SM_DING, 3, 3, + "Download disallowed in restricted mode"); + goto fini; + } + + err = NULL; + tfp = temp_nam(NULL, "pd"); + build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_DOWNLOAD_CMD_PREFIX, + ps_global->VAR_DOWNLOAD_CMD, tfp); + dprint((1, "Download cmd called: \"%s\"\n", cmd)); + if((so = so_get(FileStar, tfp, WRITE_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){ + gf_set_so_writec(&pc, so); + + for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){ + if(!(state->mail_stream + && (rawno = mn_m2raw(msgmap, i)) > 0L + && rawno <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, rawno)) + && mc->valid)) + mc = NULL; + + if(!(env = pine_mail_fetchstructure(state->mail_stream, + mn_m2raw(msgmap, i), &b)) + || !bezerk_delimiter(env, mc, pc, next++) + || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)), + env, b, NULL, FM_NEW_MESS | FM_NOWRAP, pc)){ + q_status_message(SM_ORDER | SM_DING, 3, 3, + err = "Error writing tempfile for download"); + break; + } + } + + gf_clear_so_writec(so); + if(so_give(&so)){ /* close file */ + if(!err) + err = "Error writing tempfile for download"; + } + + if(!err){ + if((syspipe = open_system_pipe(cmd, NULL, NULL, + PIPE_USER | PIPE_RESET, + 0, pipe_callback, pipe_report_error)) != NULL) + (void) close_system_pipe(&syspipe, NULL, pipe_callback); + else + q_status_message(SM_ORDER | SM_DING, 3, 3, + err = _("Error running download command")); + } + } + else + q_status_message(SM_ORDER | SM_DING, 3, 3, + err = "Error building temp file for download"); + + if(tfp){ + our_unlink(tfp); + fs_give((void **)&tfp); + } + + if(!err) + q_status_message(SM_ORDER, 0, 3, _("Download Command Completed")); + + goto fini; + } +#endif /* !(DOS || MAC) */ + + + if(rflags & GER_APPEND) + leading_nl = 1; + else + leading_nl = 0; + + dprint((5, "Opening file \"%s\" for export\n", + full_filename ? full_filename : "?")); + + if(!(store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE))){ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + /* TRANSLATORS: error opening file "<filename>" to export message: <error text> */ + _("Error opening file \"%s\" to export message: %s"), + full_filename, error_description(errno)); + goto fini; + } + else + gf_set_so_writec(&pc, store); + + err = NULL; + for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), count++){ + env = pine_mail_fetchstructure(state->mail_stream, mn_m2raw(msgmap, i), + &b); + if(!env) { + err = _("Can't export message. Error accessing mail folder"); + failure = 1; + break; + } + + if(!(state->mail_stream + && (rawno = mn_m2raw(msgmap, i)) > 0L + && rawno <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, rawno)) + && mc->valid)) + mc = NULL; + + start_of_append = so_tell(store); + if(!bezerk_delimiter(env, mc, pc, leading_nl) + || !format_message(mn_m2raw(msgmap, i), env, b, NULL, + FM_NEW_MESS | FM_NOWRAP, pc)){ + orig_errno = errno; /* save incase things are really bad */ + failure = 1; /* pop out of here */ + break; + } + + leading_nl = 1; + } + + gf_clear_so_writec(store); + if(so_give(&store)) /* release storage */ + failure++; + + if(failure){ + our_truncate(full_filename, (off_t)start_of_append); + if(err){ + dprint((1, "FAILED Export: fetch(%ld): %s\n", + i, err ? err : "?")); + q_status_message(SM_ORDER | SM_DING, 3, 4, err); + } + else{ + dprint((1, "FAILED Export: file \"%s\" : %s\n", + full_filename ? full_filename : "?", + error_description(orig_errno))); + q_status_message2(SM_ORDER | SM_DING, 3, 4, + /* TRANSLATORS: Error exporting to <filename>: <error text> */ + _("Error exporting to \"%s\" : %s"), + filename, error_description(orig_errno)); + } + } + else{ + if(rflags & GER_ALLPARTS && full_filename[0]){ + char dir[MAXPATH+1]; + char lfile[MAXPATH+1]; + int ok = 0, tries = 0, saved = 0, errs = 0; + ATTACH_S *a; + + /* + * Now we want to save all of the attachments to a subdirectory. + * To make it easier for us and probably easier for the user, and + * to prevent the user from shooting himself in the foot, we + * make a new subdirectory so that we can't possibly step on + * any existing files, and we don't need any interaction with the + * user while saving. + * + * We'll just use the directory name full_filename.d or if that + * already exists and isn't empty, we'll try adding a suffix to + * that until we get something to use. + */ + + if(strlen(full_filename) + strlen(".d") + 1 > sizeof(dir)){ + q_status_message1(SM_ORDER | SM_DING, 3, 4, + _("Can't save attachments, filename too long: %s"), + full_filename); + goto fini; + } + + ok = 0; + snprintf(dir, sizeof(dir), "%s.d", full_filename); + dir[sizeof(dir)-1] = '\0'; + + do { + tries++; + switch(r = is_writable_dir(dir)){ + case 0: /* exists and is a writable dir */ + /* + * We could figure out if it is empty and use it in + * that case, but that sounds like a lot of work, so + * just fall through to default. + */ + + default: + if(strlen(full_filename) + strlen(".d") + 1 + + 1 + strlen(long2string((long) tries)) > sizeof(dir)){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + "Problem saving attachments"); + goto fini; + } + + snprintf(dir, sizeof(dir), "%s.d_%s", full_filename, + long2string((long) tries)); + dir[sizeof(dir)-1] = '\0'; + break; + + case 3: /* doesn't exist, that's good! */ + /* make new directory */ + ok++; + break; + } + } while(!ok && tries < 1000); + + if(tries >= 1000){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Problem saving attachments")); + goto fini; + } + + /* create the new directory */ + if(our_mkdir(dir, 0700)){ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + _("Problem saving attachments: %s: %s"), dir, + error_description(errno)); + goto fini; + } + + if(!(state->mail_stream + && (rawno = mn_m2raw(msgmap, mn_get_cur(msgmap))) > 0L + && rawno <= state->mail_stream->nmsgs + && (env=pine_mail_fetchstructure(state->mail_stream,rawno,&b)) + && b)){ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Problem reading message")); + goto fini; + } + + zero_atmts(state->atmts); + describe_mime(b, "", 1, 1, 0, 0); + + a = state->atmts; + if(a && a->description) /* skip main body part */ + a++; + + for(; a->description != NULL; a++){ + /* skip over these parts of the message */ + if(MIME_MSG_A(a) || MIME_DGST_A(a) || MIME_VCARD_A(a)) + continue; + + lfile[0] = '\0'; + (void) get_filename_parameter(lfile, sizeof(lfile), a->body, NULL); + + if(lfile[0] == '\0'){ + snprintf(lfile, sizeof(lfile), "part_%.*s", sizeof(lfile)-6, + a->number ? a->number : "?"); + lfile[sizeof(lfile)-1] = '\0'; + } + + if(strlen(dir) + strlen(S_FILESEP) + strlen(lfile) + 1 + > sizeof(filename)){ + dprint((2, + "FAILED Att Export: name too long: %s\n", + dir, S_FILESEP, lfile)); + errs++; + continue; + } + + snprintf(filename, sizeof(filename), "%s%s%s", dir, S_FILESEP, lfile); + filename[sizeof(filename)-1] = '\0'; + + if(write_attachment_to_file(state->mail_stream, rawno, + a, GER_NONE, filename) == 1) + saved++; + else + errs++; + } + + if(errs){ + if(saved) + q_status_message1(SM_ORDER, 3, 3, + "Errors saving some attachments, %s attachments saved", + long2string((long) saved)); + else + q_status_message(SM_ORDER, 3, 3, + _("Problems saving attachments")); + } + else{ + if(saved) + q_status_message2(SM_ORDER, 0, 3, + /* TRANSLATORS: Saved <how many> attachements to <directory name> */ + _("Saved %s attachments to %s"), + long2string((long) saved), dir); + else + q_status_message(SM_ORDER, 3, 3, _("No attachments to save")); + } + } + else if(mn_total_cur(msgmap) > 1L) + q_status_message4(SM_ORDER,0,3, + "%s message%s %s to file \"%s\"", + long2string(count), plural(count), + rflags & GER_OVER + ? "overwritten" + : rflags & GER_APPEND ? "appended" : "exported", + filename); + else + q_status_message3(SM_ORDER,0,3, + "Message %s %s to file \"%s\"", + long2string(mn_get_cur(msgmap)), + rflags & GER_OVER + ? "overwritten" + : rflags & GER_APPEND ? "appended" : "exported", + filename); + rv++; + } + + fini: + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + return rv; +} + + +/* + * Ask user what file to export to. Export from srcstore to that file. + * + * Args ps -- pine struct + * srctext -- pointer to source text + * srctype -- type of that source text + * prompt_msg -- see get_export_filename + * lister_msg -- " + * + * Returns: != 0 : error + * 0 : ok + */ +int +simple_export(struct pine *ps, void *srctext, SourceType srctype, char *prompt_msg, char *lister_msg) +{ + int r = 1, rflags = GER_NONE; + char filename[MAXPATH+1], full_filename[MAXPATH+1]; + STORE_S *store = NULL; + struct variable *vars = ps->vars; + static HISTORY_S *history = NULL; + static ESCKEY_S simple_export_opts[] = { + {ctrl('T'), 10, "^T", N_("To Files")}, + {-1, 0, NULL, NULL}, + {-1, 0, NULL, NULL}}; + + if(F_ON(F_ENABLE_TAB_COMPLETE,ps)){ + simple_export_opts[r].ch = ctrl('I'); + simple_export_opts[r].rval = 11; + simple_export_opts[r].name = "TAB"; + simple_export_opts[r].label = N_("Complete"); + } + + if(!srctext){ + q_status_message(SM_ORDER, 0, 2, _("Error allocating space")); + r = -3; + goto fini; + } + + simple_export_opts[++r].ch = -1; + filename[0] = '\0'; + full_filename[0] = '\0'; + + r = get_export_filename(ps, filename, NULL, full_filename, sizeof(filename), + prompt_msg, lister_msg, simple_export_opts, &rflags, + -FOOTER_ROWS(ps), GE_IS_EXPORT, &history); + + if(r < 0) + goto fini; + else if(!full_filename[0]){ + r = -1; + goto fini; + } + + dprint((5, "Opening file \"%s\" for export\n", + full_filename ? full_filename : "?")); + + if((store = so_get(FileStar, full_filename, WRITE_ACCESS|WRITE_TO_LOCALE)) != NULL){ + char *pipe_err; + gf_io_t pc, gc; + + gf_set_so_writec(&pc, store); + gf_set_readc(&gc, srctext, (srctype == CharStar) + ? strlen((char *)srctext) + : 0L, + srctype, 0); + gf_filter_init(); + if((pipe_err = gf_pipe(gc, pc)) != NULL){ + q_status_message2(SM_ORDER | SM_DING, 3, 3, + /* TRANSLATORS: Problem saving to <filename>: <error text> */ + _("Problem saving to \"%s\": %s"), + filename, pipe_err); + r = -3; + } + else + r = 0; + + gf_clear_so_writec(store); + if(so_give(&store)){ + q_status_message2(SM_ORDER | SM_DING, 3, 3, + _("Problem saving to \"%s\": %s"), + filename, error_description(errno)); + r = -3; + } + } + else{ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + _("Error opening file \"%s\" for export: %s"), + full_filename, error_description(errno)); + r = -3; + } + +fini: + switch(r){ + case 0: + /* overloading full_filename */ + snprintf(full_filename, sizeof(full_filename), "%c%s", + (prompt_msg && prompt_msg[0]) + ? (islower((unsigned char)prompt_msg[0]) + ? toupper((unsigned char)prompt_msg[0]) : prompt_msg[0]) + : 'T', + (prompt_msg && prompt_msg[0]) ? prompt_msg+1 : "ext"); + full_filename[sizeof(full_filename)-1] = '\0'; + q_status_message3(SM_ORDER,0,2,"%s %s to \"%s\"", + full_filename, + rflags & GER_OVER + ? "overwritten" + : rflags & GER_APPEND ? "appended" : "exported", + filename); + break; + + case -1: + cmd_cancelled("Export"); + break; + + case -2: + q_status_message1(SM_ORDER, 0, 2, + _("Can't export to file outside of %s"), VAR_OPER_DIR); + break; + } + + ps->mangled_footer = 1; + return(r); +} + + +/* + * Ask user what file to export to. + * + * filename -- On input, this is the filename to start with. On exit, + * this is the filename chosen. (but this isn't used) + * deefault -- This is the default value if user hits return. The + * prompt will have [deefault] added to it automatically. + * full_filename -- This is the full filename on exit. + * len -- Minimum length of _both_ filename and full_filename. + * prompt_msg -- Message to insert in prompt. + * lister_msg -- Message to insert in file_lister. + * opts -- Key options. + * There is a tangled relationship between the callers + * and this routine as far as opts are concerned. Some + * of the opts are handled here. In particular, r == 3, + * r == 10, r == 11, and r == 13 are all handled here. + * Don't use those values unless you want what happens + * here. r == 12 and others are handled by the caller. + * rflags -- Return flags + * GER_OVER - overwrite of existing file + * GER_APPEND - append of existing file + * else file did not exist before + * + * GER_ALLPARTS - AllParts toggle was turned on + * + * qline -- Command line to prompt on. + * flags -- Logically OR'd flags + * GE_IS_EXPORT - The command was an Export command + * so the prompt should include + * EXPORT:. + * GE_SEQ_SENSITIVE - The command that got us here is + * sensitive to sequence number changes + * caused by unsolicited expunges. + * GE_NO_APPEND - We will not allow append to an + * existing file, only removal of the + * file if it exists. + * GE_IS_IMPORT - We are selecting for reading. + * No overwriting or checking for + * existence at all. Don't use this + * together with GE_NO_APPEND. + * GE_ALLPARTS - Turn on AllParts toggle. + * + * Returns: -1 cancelled + * -2 prohibited by VAR_OPER_DIR + * -3 other error, already reported here + * 0 ok + * 12 user chose 12 command from opts + */ +int +get_export_filename(struct pine *ps, char *filename, char *deefault, + char *full_filename, size_t len, char *prompt_msg, + char *lister_msg, ESCKEY_S *optsarg, int *rflags, + int qline, int flags, HISTORY_S **history) +{ + char dir[MAXPATH+1], dir2[MAXPATH+1]; + char precolon[MAXPATH+1], postcolon[MAXPATH+1]; + char filename2[MAXPATH+1], tmp[MAXPATH+1], *fn, *ill; + int l, i, ku = -1, r, fatal, homedir = 0, was_abs_path=0, avail, ret = 0; + int allparts = 0; + char prompt_buf[400]; + char def[500]; + ESCKEY_S *opts = NULL; + struct variable *vars = ps->vars; + + if(flags & GE_ALLPARTS || history){ + /* + * Copy the opts and add one to the end of the list. + */ + for(i = 0; optsarg[i].ch != -1; i++) + ; + + if(history) + i += 2; + + if(flags & GE_ALLPARTS) + i++; + + opts = (ESCKEY_S *) fs_get((i+1) * sizeof(*opts)); + memset(opts, 0, (i+1) * sizeof(*opts)); + + for(i = 0; optsarg[i].ch != -1; i++){ + opts[i].ch = optsarg[i].ch; + opts[i].rval = optsarg[i].rval; + opts[i].name = optsarg[i].name; /* no need to make a copy */ + opts[i].label = optsarg[i].label; /* " */ + } + + if(flags & GE_ALLPARTS){ + allparts = i; + opts[i].ch = ctrl('P'); + opts[i].rval = 13; + opts[i].name = "^P"; + /* TRANSLATORS: Export all attachment parts */ + opts[i++].label = N_("AllParts"); + } + + if(history){ + opts[i].ch = KEY_UP; + opts[i].rval = 30; + opts[i].name = ""; + ku = i; + opts[i++].label = ""; + + opts[i].ch = KEY_DOWN; + opts[i].rval = 31; + opts[i].name = ""; + opts[i++].label = ""; + } + + opts[i].ch = -1; + + if(history) + init_hist(history, HISTSIZE); + } + else + opts = optsarg; + + if(rflags) + *rflags = GER_NONE; + + if(F_ON(F_USE_CURRENT_DIR, ps)) + dir[0] = '\0'; + else if(VAR_OPER_DIR){ + strncpy(dir, VAR_OPER_DIR, sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + } +#if defined(DOS) || defined(OS2) + else if(VAR_FILE_DIR){ + strncpy(dir, VAR_FILE_DIR, sizeof(dir)); + dir[sizeof(dir)-1] = '\0'; + } +#endif + else{ + dir[0] = '~'; + dir[1] = '\0'; + homedir=1; + } + + postcolon[0] = '\0'; + strncpy(precolon, dir, sizeof(precolon)); + precolon[sizeof(precolon)-1] = '\0'; + if(deefault){ + strncpy(def, deefault, sizeof(def)-1); + def[sizeof(def)-1] = '\0'; + removing_leading_and_trailing_white_space(def); + } + else + def[0] = '\0'; + + avail = MAX(20, ps_global->ttyo ? ps_global->ttyo->screen_cols : 80) - MIN_OPT_ENT_WIDTH; + + /*---------- Prompt the user for the file name -------------*/ + while(1){ + int oeflags; + char dirb[50], fileb[50]; + int l1, l2, l3, l4, l5, needed; + char *p, p1[100], p2[100], *p3, p4[100], p5[100]; + + snprintf(p1, sizeof(p1), "%sCopy ", + (flags & GE_IS_EXPORT) ? "EXPORT: " : + (flags & GE_IS_IMPORT) ? "IMPORT: " : "SAVE: "); + p1[sizeof(p1)-1] = '\0'; + l1 = strlen(p1); + + strncpy(p2, prompt_msg ? prompt_msg : "", sizeof(p2)-1); + p2[sizeof(p2)-1] = '\0'; + l2 = strlen(p2); + + if(rflags && *rflags & GER_ALLPARTS) + p3 = " (and atts)"; + else + p3 = ""; + + l3 = strlen(p3); + + snprintf(p4, sizeof(p4), " %s file%s%s", + (flags & GE_IS_IMPORT) ? "from" : "to", + is_absolute_path(filename) ? "" : " in ", + is_absolute_path(filename) ? "" : + (!dir[0] ? "current directory" + : (dir[0] == '~' && !dir[1]) ? "home directory" + : short_str(dir,dirb,sizeof(dirb),30,FrontDots))); + p4[sizeof(p4)-1] = '\0'; + l4 = strlen(p4); + + snprintf(p5, sizeof(p5), "%s%s%s: ", + *def ? " [" : "", + *def ? short_str(def,fileb,sizeof(fileb),40,EndDots) : "", + *def ? "]" : ""); + p5[sizeof(p5)-1] = '\0'; + l5 = strlen(p5); + + if((needed = l1+l2+l3+l4+l5-avail) > 0){ + snprintf(p4, sizeof(p4), " %s file%s%s", + (flags & GE_IS_IMPORT) ? "from" : "to", + is_absolute_path(filename) ? "" : " in ", + is_absolute_path(filename) ? "" : + (!dir[0] ? "current dir" + : (dir[0] == '~' && !dir[1]) ? "home dir" + : short_str(dir,dirb,sizeof(dirb),10,FrontDots))); + p4[sizeof(p4)-1] = '\0'; + l4 = strlen(p4); + } + + if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){ + snprintf(p5, sizeof(p5), "%s%s%s: ", + *def ? " [" : "", + *def ? short_str(def,fileb,sizeof(fileb), + MAX(15,l5-5-needed),EndDots) : "", + *def ? "]" : ""); + p5[sizeof(p5)-1] = '\0'; + l5 = strlen(p5); + } + + if((needed = l1+l2+l3+l4+l5-avail) > 0 && l2 > 0){ + + /* + * 14 is about the shortest we can make this, because there are + * fixed length strings of length 14 coming in here. + */ + p = short_str(prompt_msg, p2, sizeof(p2), MAX(14,l2-needed), FrontDots); + if(p != p2){ + strncpy(p2, p, sizeof(p2)-1); + p2[sizeof(p2)-1] = '\0'; + } + + l2 = strlen(p2); + } + + if((needed = l1+l2+l3+l4+l5-avail) > 0){ + strncpy(p1, "Copy ", sizeof(p1)-1); + p1[sizeof(p1)-1] = '\0'; + l1 = strlen(p1); + } + + if((needed = l1+l2+l3+l4+l5-avail) > 0 && l5 > 0){ + snprintf(p5, sizeof(p5), "%s%s%s: ", + *def ? " [" : "", + *def ? short_str(def,fileb, sizeof(fileb), + MAX(10,l5-5-needed),EndDots) : "", + *def ? "]" : ""); + p5[sizeof(p5)-1] = '\0'; + l5 = strlen(p5); + } + + if((needed = l1+l2+l3+l4+l5-avail) > 0 && l3 > 0){ + if(needed <= l3 - strlen(" (+ atts)")) + p3 = " (+ atts)"; + else if(needed <= l3 - strlen(" (atts)")) + p3 = " (atts)"; + else if(needed <= l3 - strlen(" (+)")) + p3 = " (+)"; + else if(needed <= l3 - strlen("+")) + p3 = "+"; + else + p3 = ""; + + l3 = strlen(p3); + } + + snprintf(prompt_buf, sizeof(prompt_buf), "%s%s%s%s%s", p1, p2, p3, p4, p5); + prompt_buf[sizeof(prompt_buf)-1] = '\0'; + + if(ku >= 0){ + if(items_in_hist(*history) > 0){ + opts[ku].name = HISTORY_UP_KEYNAME; + opts[ku].label = HISTORY_KEYLABEL; + opts[ku+1].name = HISTORY_DOWN_KEYNAME; + opts[ku+1].label = HISTORY_KEYLABEL; + } + else{ + opts[ku].name = ""; + opts[ku].label = ""; + opts[ku+1].name = ""; + opts[ku+1].label = ""; + } + } + + oeflags = OE_APPEND_CURRENT | + ((flags & GE_SEQ_SENSITIVE) ? OE_SEQ_SENSITIVE : 0); + r = optionally_enter(filename, qline, 0, len, prompt_buf, + opts, NO_HELP, &oeflags); + + /*--- Help ----*/ + if(r == 3){ + /* + * Helps may not be right if you add another caller or change + * things. Check it out. + */ + if(flags & GE_IS_IMPORT) + helper(h_ge_import, _("HELP FOR IMPORT FILE SELECT"), HLPD_SIMPLE); + else if(flags & GE_ALLPARTS) + helper(h_ge_allparts, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE); + else + helper(h_ge_export, _("HELP FOR EXPORT FILE SELECT"), HLPD_SIMPLE); + + ps->mangled_screen = 1; + + continue; + } + else if(r == 10 || r == 11){ /* Browser or File Completion */ + if(filename[0]=='~'){ + if(filename[1] == C_FILESEP && filename[2]!='\0'){ + precolon[0] = '~'; + precolon[1] = '\0'; + for(i=0; filename[i+2] != '\0' && i+2 < len-1; i++) + filename[i] = filename[i+2]; + filename[i] = '\0'; + strncpy(dir, precolon, sizeof(dir)-1); + dir[sizeof(dir)-1] = '\0'; + } + else if(filename[1]=='\0' || + (filename[1] == C_FILESEP && filename[2] == '\0')){ + precolon[0] = '~'; + precolon[1] = '\0'; + filename[0] = '\0'; + strncpy(dir, precolon, sizeof(dir)-1); + dir[sizeof(dir)-1] = '\0'; + } + } + else if(!dir[0] && !is_absolute_path(filename) && was_abs_path){ + if(homedir){ + precolon[0] = '~'; + precolon[1] = '\0'; + strncpy(dir, precolon, sizeof(dir)-1); + dir[sizeof(dir)-1] = '\0'; + } + else{ + precolon[0] = '\0'; + dir[0] = '\0'; + } + } + l = MAXPATH; + dir2[0] = '\0'; + strncpy(tmp, filename, sizeof(tmp)-1); + tmp[sizeof(tmp)-1] = '\0'; + if(*tmp && is_absolute_path(tmp)) + fnexpand(tmp, sizeof(tmp)); + if(strncmp(tmp,postcolon, strlen(postcolon))) + postcolon[0] = '\0'; + + if(*tmp && (fn = last_cmpnt(tmp))){ + l -= fn - tmp; + strncpy(filename2, fn, sizeof(filename2)-1); + filename2[sizeof(filename2)-1] = '\0'; + if(is_absolute_path(tmp)){ + strncpy(dir2, tmp, MIN(fn - tmp, sizeof(dir2)-1)); + dir2[MIN(fn - tmp, sizeof(dir2)-1)] = '\0'; +#ifdef _WINDOWS + if(tmp[1]==':' && tmp[2]=='\\' && dir2[2]=='\0'){ + dir2[2] = '\\'; + dir2[3] = '\0'; + } +#endif + strncpy(postcolon, dir2, sizeof(postcolon)-1); + postcolon[sizeof(postcolon)-1] = '\0'; + precolon[0] = '\0'; + } + else{ + char *p = NULL; + /* + * Just building the directory name in dir2, + * full_filename is overloaded. + */ + snprintf(full_filename, len, "%.*s", MIN(fn-tmp,len-1), tmp); + full_filename[len-1] = '\0'; + strncpy(postcolon, full_filename, sizeof(postcolon)-1); + postcolon[sizeof(postcolon)-1] = '\0'; + build_path(dir2, !dir[0] ? p = (char *)getcwd(NULL,MAXPATH) + : (dir[0] == '~' && !dir[1]) + ? ps->home_dir + : dir, + full_filename, sizeof(dir2)); + if(p) + free(p); + } + } + else{ + if(is_absolute_path(tmp)){ + strncpy(dir2, tmp, sizeof(dir2)-1); + dir2[sizeof(dir2)-1] = '\0'; +#ifdef _WINDOWS + if(dir2[2]=='\0' && dir2[1]==':'){ + dir2[2]='\\'; + dir2[3]='\0'; + strncpy(postcolon,dir2,sizeof(postcolon)-1); + postcolon[sizeof(postcolon)-1] = '\0'; + } +#endif + filename2[0] = '\0'; + precolon[0] = '\0'; + } + else{ + strncpy(filename2, tmp, sizeof(filename2)-1); + filename2[sizeof(filename2)-1] = '\0'; + if(!dir[0]) + (void)getcwd(dir2, sizeof(dir2)); + else if(dir[0] == '~' && !dir[1]){ + strncpy(dir2, ps->home_dir, sizeof(dir2)-1); + dir2[sizeof(dir2)-1] = '\0'; + } + else{ + strncpy(dir2, dir, sizeof(dir2)-1); + dir2[sizeof(dir2)-1] = '\0'; + } + + postcolon[0] = '\0'; + } + } + + build_path(full_filename, dir2, filename2, len); + if(!strcmp(full_filename, dir2)) + filename2[0] = '\0'; + if(full_filename[strlen(full_filename)-1] == C_FILESEP + && isdir(full_filename,NULL,NULL)){ + if(strlen(full_filename) == 1) + strncpy(postcolon, full_filename, sizeof(postcolon)-1); + else if(filename2[0]) + strncpy(postcolon, filename2, sizeof(postcolon)-1); + postcolon[sizeof(postcolon)-1] = '\0'; + strncpy(dir2, full_filename, sizeof(dir2)-1); + dir2[sizeof(dir2)-1] = '\0'; + filename2[0] = '\0'; + } +#ifdef _WINDOWS /* use full_filename even if not a valid directory */ + else if(full_filename[strlen(full_filename)-1] == C_FILESEP){ + strncpy(postcolon, filename2, sizeof(postcolon)-1); + postcolon[sizeof(postcolon)-1] = '\0'; + strncpy(dir2, full_filename, sizeof(dir2)-1); + dir2[sizeof(dir2)-1] = '\0'; + filename2[0] = '\0'; + } +#endif + if(dir2[strlen(dir2)-1] == C_FILESEP && strlen(dir2)!=1 + && strcmp(dir2+1, ":\\")) + /* last condition to prevent stripping of '\\' + in windows partition */ + dir2[strlen(dir2)-1] = '\0'; + + if(r == 10){ /* File Browser */ + r = file_lister(lister_msg ? lister_msg : "EXPORT", + dir2, sizeof(dir2), filename2, sizeof(filename2), + TRUE, + (flags & GE_IS_IMPORT) ? FB_READ : FB_SAVE); +#ifdef _WINDOWS +/* Windows has a special "feature" in which entering the file browser will + change the working directory if the directory is changed at all (even + clicking "Cancel" will change the working directory). +*/ + if(F_ON(F_USE_CURRENT_DIR, ps)) + (void)getcwd(dir2,sizeof(dir2)); +#endif + if(isdir(dir2,NULL,NULL)){ + strncpy(precolon, dir2, sizeof(precolon)-1); + precolon[sizeof(precolon)-1] = '\0'; + } + strncpy(postcolon, filename2, sizeof(postcolon)-1); + postcolon[sizeof(postcolon)-1] = '\0'; + if(r == 1){ + build_path(full_filename, dir2, filename2, len); + if(isdir(full_filename, NULL, NULL)){ + strncpy(dir, full_filename, sizeof(dir)-1); + dir[sizeof(dir)-1] = '\0'; + filename[0] = '\0'; + } + else{ + fn = last_cmpnt(full_filename); + strncpy(dir, full_filename, + MIN(fn - full_filename, sizeof(dir)-1)); + dir[MIN(fn - full_filename, sizeof(dir)-1)] = '\0'; + if(fn - full_filename > 1) + dir[fn - full_filename - 1] = '\0'; + } + + if(!strcmp(dir, ps->home_dir)){ + dir[0] = '~'; + dir[1] = '\0'; + } + + strncpy(filename, fn, len-1); + filename[len-1] = '\0'; + } + } + else{ /* File Completion */ + if(!pico_fncomplete(dir2, filename2, sizeof(filename2))) + Writechar(BELL, 0); + strncat(postcolon, filename2, + sizeof(postcolon)-1-strlen(postcolon)); + postcolon[sizeof(postcolon)-1] = '\0'; + + was_abs_path = is_absolute_path(filename); + + if(!strcmp(dir, ps->home_dir)){ + dir[0] = '~'; + dir[1] = '\0'; + } + } + strncpy(filename, postcolon, len-1); + filename[len-1] = '\0'; + strncpy(dir, precolon, sizeof(dir)-1); + dir[sizeof(dir)-1] = '\0'; + + if(filename[0] == '~' && !filename[1]){ + dir[0] = '~'; + dir[1] = '\0'; + filename[0] = '\0'; + } + + continue; + } + else if(r == 12){ /* Download, caller handles it */ + ret = r; + goto done; + } + else if(r == 13){ /* toggle AllParts bit */ + if(rflags){ + if(*rflags & GER_ALLPARTS){ + *rflags &= ~GER_ALLPARTS; + opts[allparts].label = N_("AllParts"); + } + else{ + *rflags |= GER_ALLPARTS; + /* opposite of All Parts, No All Parts */ + opts[allparts].label = N_("NoAllParts"); + } + } + + continue; + } +#if 0 + else if(r == 14){ /* List file names matching partial? */ + continue; + } +#endif + else if(r == 1){ /* Cancel */ + ret = -1; + goto done; + } + else if(r == 4){ + continue; + } + else if(r == 30 || r == 31){ + char *p = NULL; + + if(history){ + if(r == 30) + p = get_prev_hist(*history, filename, 0, NULL); + else + p = get_next_hist(*history, filename, 0, NULL); + } + + if(p != NULL){ + fn = last_cmpnt(p); + strncpy(dir, p, MIN(fn - p, sizeof(dir)-1)); + dir[MIN(fn - p, sizeof(dir)-1)] = '\0'; + if(fn - p > 1) + dir[fn - p - 1] = '\0'; + + if(!strcmp(dir, ps->home_dir)){ + dir[0] = '~'; + dir[1] = '\0'; + } + + strncpy(filename, fn, len-1); + filename[len-1] = '\0'; + } + else + Writechar(BELL, 0); + + continue; + } + else if(r != 0){ + Writechar(BELL, 0); + continue; + } + + removing_leading_and_trailing_white_space(filename); + + if(!*filename){ + if(!*def){ /* Cancel */ + ret = -1; + goto done; + } + + strncpy(filename, def, len-1); + filename[len-1] = '\0'; + } + +#if defined(DOS) || defined(OS2) + if(is_absolute_path(filename)){ + fixpath(filename, len); + } +#else + if(filename[0] == '~'){ + if(fnexpand(filename, len) == NULL){ + char *p = strindex(filename, '/'); + if(p != NULL) + *p = '\0'; + q_status_message1(SM_ORDER | SM_DING, 3, 3, + _("Error expanding file name: \"%s\" unknown user"), + filename); + continue; + } + } +#endif + + if(is_absolute_path(filename)){ + strncpy(full_filename, filename, len-1); + full_filename[len-1] = '\0'; + } + else{ + if(!dir[0]) + build_path(full_filename, (char *)getcwd(dir,sizeof(dir)), + filename, len); + else if(dir[0] == '~' && !dir[1]) + build_path(full_filename, ps->home_dir, filename, len); + else + build_path(full_filename, dir, filename, len); + } + + if((ill = filter_filename(full_filename, &fatal, + ps_global->restricted || ps_global->VAR_OPER_DIR)) != NULL){ + if(fatal){ + q_status_message1(SM_ORDER | SM_DING, 3, 3, "%s", ill); + continue; + } + else{ +/* BUG: we should beep when the key's pressed rather than bitch later */ + /* Warn and ask for confirmation. */ + snprintf(prompt_buf, sizeof(prompt_buf), "File name contains a '%s'. %s anyway", + ill, (flags & GE_IS_EXPORT) ? "Export" : "Save"); + prompt_buf[sizeof(prompt_buf)-1] = '\0'; + if(want_to(prompt_buf, 'n', 0, NO_HELP, + ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0)) != 'y') + continue; + } + } + + break; /* Must have got an OK file name */ + } + + if(VAR_OPER_DIR && !in_dir(VAR_OPER_DIR, full_filename)){ + ret = -2; + goto done; + } + + if(!can_access(full_filename, ACCESS_EXISTS)){ + int rbflags; + static ESCKEY_S access_opts[] = { + /* TRANSLATORS: asking user if they want to overwrite (replace contents of) + a file or append to the end of the file */ + {'o', 'o', "O", N_("Overwrite")}, + {'a', 'a', "A", N_("Append")}, + {-1, 0, NULL, NULL}}; + + rbflags = RB_NORM | ((flags & GE_SEQ_SENSITIVE) ? RB_SEQ_SENSITIVE : 0); + + if(flags & GE_NO_APPEND){ + r = strlen(filename); + snprintf(prompt_buf, sizeof(prompt_buf), + /* TRANSLATORS: asking user whether to overwrite a file or not, + File <filename> already exists. Overwrite it ? */ + _("File \"%s%s\" already exists. Overwrite it "), + (r > 20) ? "..." : "", + filename + ((r > 20) ? r - 20 : 0)); + prompt_buf[sizeof(prompt_buf)-1] = '\0'; + if(want_to(prompt_buf, 'n', 'x', NO_HELP, rbflags) == 'y'){ + if(rflags) + *rflags |= GER_OVER; + + if(our_unlink(full_filename) < 0){ + q_status_message2(SM_ORDER | SM_DING, 3, 5, + /* TRANSLATORS: Cannot remove old <filename>: <error text> */ + _("Cannot remove old %s: %s"), + full_filename, error_description(errno)); + } + } + else{ + ret = -1; + goto done; + } + } + else if(!(flags & GE_IS_IMPORT)){ + r = strlen(filename); + snprintf(prompt_buf, sizeof(prompt_buf), + /* TRANSLATORS: File <filename> already exists. Overwrite or append to it ? */ + _("File \"%s%s\" already exists. Overwrite or append to it ? "), + (r > 20) ? "..." : "", + filename + ((r > 20) ? r - 20 : 0)); + prompt_buf[sizeof(prompt_buf)-1] = '\0'; + switch(radio_buttons(prompt_buf, -FOOTER_ROWS(ps_global), + access_opts, 'a', 'x', NO_HELP, rbflags)){ + case 'o' : + if(rflags) + *rflags |= GER_OVER; + + if(our_truncate(full_filename, (off_t)0) < 0) + /* trouble truncating, but we'll give it a try anyway */ + q_status_message2(SM_ORDER | SM_DING, 3, 5, + /* TRANSLATORS: Warning: Cannot truncate old <filename>: <error text> */ + _("Warning: Cannot truncate old %s: %s"), + full_filename, error_description(errno)); + break; + + case 'a' : + if(rflags) + *rflags |= GER_APPEND; + + break; + + case 'x' : + default : + ret = -1; + goto done; + } + } + } + +done: + if(history && ret == 0) + save_hist(*history, full_filename, 0, NULL); + + if(opts && opts != optsarg) + fs_give((void **) &opts); + + return(ret); +} + + +/*---------------------------------------------------------------------- + parse the config'd upload/download command + + Args: cmd -- buffer to return command fit for shellin' + prefix -- + cfg_str -- + fname -- file name to build into the command + + Returns: pointer to cmd_str buffer or NULL on real bad error + + NOTE: One SIDE EFFECT is that any defined "prefix" string in the + cfg_str is written to standard out right before a successful + return of this function. The call immediately following this + function darn well better be the shell exec... + ----*/ +char * +build_updown_cmd(char *cmd, size_t cmdlen, char *prefix, char *cfg_str, char *fname) +{ + char *p; + int fname_found = 0; + + if(prefix && *prefix){ + /* loop thru replacing all occurances of _FILE_ */ + p = strncpy(cmd, prefix, cmdlen); + cmd[cmdlen-1] = '\0'; + while((p = strstr(p, "_FILE_"))) + rplstr(p, cmdlen-(p-cmd), 6, fname); + + fputs(cmd, stdout); + } + + /* loop thru replacing all occurances of _FILE_ */ + p = strncpy(cmd, cfg_str, cmdlen); + cmd[cmdlen-1] = '\0'; + while((p = strstr(p, "_FILE_"))){ + rplstr(p, cmdlen-(p-cmd), 6, fname); + fname_found = 1; + } + + if(!fname_found) + snprintf(cmd+strlen(cmd), cmdlen-strlen(cmd), " %s", fname); + + cmd[cmdlen-1] = '\0'; + + dprint((4, "\n - build_updown_cmd = \"%s\" -\n", + cmd ? cmd : "?")); + return(cmd); +} + + +/*---------------------------------------------------------------------- + Write a berzerk format message delimiter using the given putc function + + Args: e -- envelope of message to write + pc -- function to use + + Returns: TRUE if we could write it, FALSE if there was a problem + + NOTE: follows delimiter with OS-dependent newline + ----*/ +int +bezerk_delimiter(ENVELOPE *env, MESSAGECACHE *mc, gf_io_t pc, int leading_newline) +{ + MESSAGECACHE telt; + time_t when; + char *p; + + /* write "[\n]From mailbox[@host] " */ + if(!((leading_newline ? gf_puts(NEWLINE, pc) : 1) + && gf_puts("From ", pc) + && gf_puts((env && env->from) ? env->from->mailbox + : "the-concourse-on-high", pc) + && gf_puts((env && env->from && env->from->host) ? "@" : "", pc) + && gf_puts((env && env->from && env->from->host) ? env->from->host + : "", pc) + && (*pc)(' '))) + return(0); + + if(mc && mc->valid) + when = mail_longdate(mc); + else if(env && env->date && env->date[0] + && mail_parse_date(&telt,env->date)) + when = mail_longdate(&telt); + else + when = time(0); + + p = ctime(&when); + + while(p && *p && *p != '\n') /* write date */ + if(!(*pc)(*p++)) + return(0); + + if(!gf_puts(NEWLINE, pc)) /* write terminating newline */ + return(0); + + return(1); +} + + +/*---------------------------------------------------------------------- + Execute command to jump to a given message number + + Args: qline -- Line to ask question on + + Result: returns true if the use selected a new message, false otherwise + + ----*/ +long +jump_to(MSGNO_S *msgmap, int qline, UCS first_num, SCROLL_S *sparms, CmdWhere in_index) +{ + char jump_num_string[80], *j, prompt[70]; + HelpType help; + int rc; + static ESCKEY_S jump_to_key[] = { {0, 0, NULL, NULL}, + /* TRANSLATORS: go to First Message */ + {ctrl('Y'), 10, "^Y", N_("First Msg")}, + {ctrl('V'), 11, "^V", N_("Last Msg")}, + {-1, 0, NULL, NULL} }; + + dprint((4, "\n - jump_to -\n")); + +#ifdef DEBUG + if(sparms && sparms->jump_is_debug) + return(get_level(qline, first_num, sparms)); +#endif + + if(!any_messages(msgmap, NULL, "to Jump to")) + return(0L); + + if(first_num && first_num < 0x80 && isdigit((unsigned char) first_num)){ + jump_num_string[0] = first_num; + jump_num_string[1] = '\0'; + } + else + jump_num_string[0] = '\0'; + + if(mn_total_cur(msgmap) > 1L){ + snprintf(prompt, sizeof(prompt), "Unselect %s msgs in favor of number to be entered", + comatose(mn_total_cur(msgmap))); + prompt[sizeof(prompt)-1] = '\0'; + if((rc = want_to(prompt, 'n', 0, NO_HELP, WT_NORM)) == 'n') + return(0L); + } + + snprintf(prompt, sizeof(prompt), "%s number to jump to : ", in_index == ThrdIndx + ? "Thread" + : "Message"); + prompt[sizeof(prompt)-1] = '\0'; + + help = NO_HELP; + while(1){ + int flags = OE_APPEND_CURRENT; + + rc = optionally_enter(jump_num_string, qline, 0, + sizeof(jump_num_string), prompt, + jump_to_key, help, &flags); + if(rc == 3){ + help = help == NO_HELP + ? (in_index == ThrdIndx ? h_oe_jump_thd : h_oe_jump) + : NO_HELP; + continue; + } + else if(rc == 10 || rc == 11){ + char warning[100]; + long closest; + + closest = closest_jump_target(rc == 10 ? 1L + : ((in_index == ThrdIndx) + ? msgmap->max_thrdno + : mn_get_total(msgmap)), + ps_global->mail_stream, + msgmap, 0, + in_index, warning, sizeof(warning)); + /* ignore warning */ + return(closest); + } + + /* + * If we take out the *jump_num_string nonempty test in this if + * then the closest_jump_target routine will offer a jump to the + * last message. However, it is slow because you have to wait for + * the status message and it is annoying for people who hit J command + * by mistake and just want to hit return to do nothing, like has + * always worked. So the test is there for now. Hubert 2002-08-19 + * + * Jumping to first/last message is now possible through ^Y/^V + * commands above. jpf 2002-08-21 + * (and through "end" hubert 2006-07-07) + */ + if(rc == 0 && *jump_num_string != '\0'){ + removing_leading_and_trailing_white_space(jump_num_string); + for(j=jump_num_string; isdigit((unsigned char)*j) || *j=='-'; j++) + ; + + if(*j != '\0'){ + if(!strucmp("end", j)) + return((in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap)); + + q_status_message(SM_ORDER | SM_DING, 2, 2, + _("Invalid number entered. Use only digits 0-9")); + jump_num_string[0] = '\0'; + } + else{ + char warning[100]; + long closest, jump_num; + + if(*jump_num_string) + jump_num = atol(jump_num_string); + else + jump_num = -1L; + + warning[0] = '\0'; + closest = closest_jump_target(jump_num, ps_global->mail_stream, + msgmap, + *jump_num_string ? 0 : 1, + in_index, warning, sizeof(warning)); + if(warning[0]) + q_status_message(SM_ORDER | SM_DING, 2, 2, warning); + + if(closest == jump_num) + return(jump_num); + + if(closest == 0L) + jump_num_string[0] = '\0'; + else + strncpy(jump_num_string, long2string(closest), + sizeof(jump_num_string)); + } + + continue; + } + + if(rc != 4) + break; + } + + return(0L); +} + + +/* + * cmd_delete_action - handle msgno advance and such after single message deletion + */ +char * +cmd_delete_action(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index) +{ + int opts; + long msgno; + char *rv = NULL; + + msgno = mn_get_cur(msgmap); + advance_cur_after_delete(state, state->mail_stream, msgmap, in_index); + + if(IS_NEWS(state->mail_stream) + || ((state->context_current->use & CNTXT_INCMNG) + && context_isambig(state->cur_folder))){ + + opts = (NSF_TRUST_FLAGS | NSF_SKIP_CHID); + if(in_index == View) + opts &= ~NSF_SKIP_CHID; + + (void)next_sorted_flagged(F_UNDEL|F_UNSEEN, state->mail_stream, msgno, &opts); + if(!(opts & NSF_FLAG_MATCH)){ + char nextfolder[MAXPATH]; + + strncpy(nextfolder, state->cur_folder, sizeof(nextfolder)); + nextfolder[sizeof(nextfolder)-1] = '\0'; + rv = next_folder(NULL, nextfolder, sizeof(nextfolder), nextfolder, + state->context_current, NULL, NULL) + ? ". Press TAB for next folder." + : ". No more folders to TAB to."; + } + } + + return(rv); +} + + +/* + * cmd_delete_index - fixup msgmap or whatever after cmd_delete has done it's thing + */ +char * +cmd_delete_index(struct pine *state, MSGNO_S *msgmap) +{ + return(cmd_delete_action(state, msgmap,MsgIndx)); +} + +/* + * cmd_delete_view - fixup msgmap or whatever after cmd_delete has done it's thing + */ +char * +cmd_delete_view(struct pine *state, MSGNO_S *msgmap) +{ + return(cmd_delete_action(state, msgmap, View)); +} + + +void +advance_cur_after_delete(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, CmdWhere in_index) +{ + long new_msgno, msgno; + int opts; + + new_msgno = msgno = mn_get_cur(msgmap); + opts = NSF_TRUST_FLAGS; + + if(F_ON(F_DEL_SKIPS_DEL, state)){ + + if(THREADING() && sp_viewing_a_thread(stream)) + opts |= NSF_SKIP_CHID; + + new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts); + } + else{ + mn_inc_cur(stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + new_msgno = mn_get_cur(msgmap); + if(new_msgno != msgno) + opts |= NSF_FLAG_MATCH; + } + + /* + * Viewing_a_thread is the complicated case because we want to ignore + * other threads at first and then look in other threads if we have to. + * By ignoring other threads we also ignore collapsed partial threads + * in our own thread. + */ + if(THREADING() && sp_viewing_a_thread(stream) && !(opts & NSF_FLAG_MATCH)){ + long rawno, orig_thrdno; + PINETHRD_S *thrd, *topthrd = NULL; + + rawno = mn_m2raw(msgmap, msgno); + thrd = fetch_thread(stream, rawno); + if(thrd && thrd->top) + topthrd = fetch_thread(stream, thrd->top); + + orig_thrdno = topthrd ? topthrd->thrdno : -1L; + + opts = NSF_TRUST_FLAGS; + new_msgno = next_sorted_flagged(F_UNDEL, stream, msgno, &opts); + + /* + * If we got a match, new_msgno may be a message in + * a different thread from the one we are viewing, or it could be + * in a collapsed part of this thread. + */ + if(opts & NSF_FLAG_MATCH){ + int ret; + char pmt[128]; + + topthrd = NULL; + thrd = fetch_thread(stream, mn_m2raw(msgmap,new_msgno)); + if(thrd && thrd->top) + topthrd = fetch_thread(stream, thrd->top); + + /* + * If this match is in the same thread we're already in + * then we're done, else we have to ask the user and maybe + * switch threads. + */ + if(!(orig_thrdno > 0L && topthrd + && topthrd->thrdno == orig_thrdno)){ + + if(F_OFF(F_AUTO_OPEN_NEXT_UNREAD, state)){ + if(in_index == View) + snprintf(pmt, sizeof(pmt), + "View message in thread number %.10s", + topthrd ? comatose(topthrd->thrdno) : "?"); + else + snprintf(pmt, sizeof(pmt), "View thread number %.10s", + topthrd ? comatose(topthrd->thrdno) : "?"); + + ret = want_to(pmt, 'y', 'x', NO_HELP, WT_NORM); + } + else + ret = 'y'; + + if(ret == 'y'){ + unview_thread(state, stream, msgmap); + mn_set_cur(msgmap, new_msgno); + if(THRD_AUTO_VIEW() + && (count_lflags_in_thread(stream, topthrd, msgmap, + MN_NONE) == 1) + && view_thread(state, stream, msgmap, 1)){ + if(current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + state->view_skipped_index = 1; + state->next_screen = mail_view_screen; + } + else{ + view_thread(state, stream, msgmap, 1); + if(current_index_state) + msgmap->top_after_thrd = current_index_state->msg_at_top; + + state->next_screen = SCREEN_FUN_NULL; + } + } + else + new_msgno = msgno; /* stick with original */ + } + } + } + + mn_set_cur(msgmap, new_msgno); + if(in_index != View) + adjust_cur_to_visible(stream, msgmap); +} + + +#ifdef DEBUG +long +get_level(int qline, UCS first_num, SCROLL_S *sparms) +{ + char debug_num_string[80], *j, prompt[70]; + HelpType help; + int rc; + long debug_num; + + if(first_num && first_num < 0x80 && isdigit((unsigned char)first_num)){ + debug_num_string[0] = first_num; + debug_num_string[1] = '\0'; + debug_num = atol(debug_num_string); + *(int *)(sparms->proc.data.p) = debug_num; + q_status_message1(SM_ORDER, 0, 3, "Show debug <= level %s", + comatose(debug_num)); + return(1L); + } + else + debug_num_string[0] = '\0'; + + snprintf(prompt, sizeof(prompt), "Show debug <= this level (0-%d) : ", MAX(debug, 9)); + prompt[sizeof(prompt)-1] = '\0'; + + help = NO_HELP; + while(1){ + int flags = OE_APPEND_CURRENT; + + rc = optionally_enter(debug_num_string, qline, 0, + sizeof(debug_num_string), prompt, + NULL, help, &flags); + if(rc == 3){ + help = help == NO_HELP ? h_oe_debuglevel : NO_HELP; + continue; + } + + if(rc == 0){ + removing_leading_and_trailing_white_space(debug_num_string); + for(j=debug_num_string; isdigit((unsigned char)*j); j++) + ; + + if(*j != '\0'){ + q_status_message(SM_ORDER | SM_DING, 2, 2, + _("Invalid number entered. Use only digits 0-9")); + debug_num_string[0] = '\0'; + } + else{ + debug_num = atol(debug_num_string); + if(debug_num < 0) + q_status_message(SM_ORDER | SM_DING, 2, 2, + _("Number should be >= 0")); + else if(debug_num > MAX(debug,9)) + q_status_message1(SM_ORDER | SM_DING, 2, 2, + _("Maximum is %s"), comatose(MAX(debug,9))); + else{ + *(int *)(sparms->proc.data.p) = debug_num; + q_status_message1(SM_ORDER, 0, 3, + "Show debug <= level %s", + comatose(debug_num)); + return(1L); + } + } + + continue; + } + + if(rc != 4) + break; + } + + return(0L); +} +#endif /* DEBUG */ + + +/* + * Returns the message number closest to target that isn't hidden. + * Make warning at least 100 chars. + * A return of 0 means there is no message to jump to. + */ +long +closest_jump_target(long int target, MAILSTREAM *stream, MSGNO_S *msgmap, int no_target, CmdWhere in_index, char *warning, size_t warninglen) +{ + long i, start, closest = 0L; + char buf[80]; + long maxnum; + + warning[0] = '\0'; + maxnum = (in_index == ThrdIndx) ? msgmap->max_thrdno : mn_get_total(msgmap); + + if(no_target){ + target = maxnum; + start = 1L; + snprintf(warning, warninglen, "No %s number entered, jump to end? ", + (in_index == ThrdIndx) ? "thread" : "message"); + warning[warninglen-1] = '\0'; + } + else if(target < 1L) + start = 1L - target; + else if(target > maxnum) + start = target - maxnum; + else + start = 1L; + + if(target > 0L && target <= maxnum) + if(in_index == ThrdIndx + || !msgline_hidden(stream, msgmap, target, 0)) + return(target); + + for(i = start; target+i <= maxnum || target-i > 0L; i++){ + + if(target+i > 0L && target+i <= maxnum && + (in_index == ThrdIndx + || !msgline_hidden(stream, msgmap, target+i, 0))){ + closest = target+i; + break; + } + + if(target-i > 0L && target-i <= maxnum && + (in_index == ThrdIndx + || !msgline_hidden(stream, msgmap, target-i, 0))){ + closest = target-i; + break; + } + } + + strncpy(buf, long2string(closest), sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + + if(closest == 0L) + strncpy(warning, "Nothing to jump to", warninglen); + else if(target < 1L) + snprintf(warning, warninglen, "%s number (%s) must be at least %s", + (in_index == ThrdIndx) ? "Thread" : "Message", + long2string(target), buf); + else if(target > maxnum) + snprintf(warning, warninglen, "%s number (%s) may be no more than %s", + (in_index == ThrdIndx) ? "Thread" : "Message", + long2string(target), buf); + else if(!no_target) + snprintf(warning, warninglen, + "Message number (%s) is not in \"Zoomed Index\" - Closest is(%s)", + long2string(target), buf); + + warning[warninglen-1] = '\0'; + + return(closest); +} + + +/*---------------------------------------------------------------------- + Prompt for folder name to open, expand the name and return it + + Args: qline -- Screen line to prompt on + allow_list -- if 1, allow ^T to bring up collection lister + + Result: returns the folder name or NULL + pine structure mangled_footer flag is set + may call the collection lister in which case mangled screen will be set + + This prompts the user for the folder to open, possibly calling up +the collection lister if the user types ^T. +----------------------------------------------------------------------*/ +char * +broach_folder(int qline, int allow_list, int *notrealinbox, CONTEXT_S **context) +{ + HelpType help; + static char newfolder[MAILTMPLEN]; + char expanded[MAXPATH+1], + prompt[MAX_SCREEN_COLS+1], + *last_folder, *p; + static HISTORY_S *history = NULL; + CONTEXT_S *tc, *tc2; + ESCKEY_S ekey[9]; + int rc, r, ku = -1, n, flags, last_rc = 0, inbox, done = 0; + + /* + * the idea is to provide a clue for the context the file name + * will be saved in (if a non-imap names is typed), and to + * only show the previous if it was also in the same context + */ + help = NO_HELP; + *expanded = '\0'; + *newfolder = '\0'; + last_folder = NULL; + if(notrealinbox) + (*notrealinbox) = 1; + + init_hist(&history, HISTSIZE); + + tc = broach_get_folder(context ? *context : NULL, &inbox, NULL); + + /* set up extra command option keys */ + rc = 0; + ekey[rc].ch = (allow_list) ? ctrl('T') : 0 ; + ekey[rc].rval = (allow_list) ? 2 : 0; + ekey[rc].name = (allow_list) ? "^T" : ""; + ekey[rc++].label = (allow_list) ? N_("ToFldrs") : ""; + + if(ps_global->context_list->next){ + ekey[rc].ch = ctrl('P'); + ekey[rc].rval = 10; + ekey[rc].name = "^P"; + ekey[rc++].label = N_("Prev Collection"); + + ekey[rc].ch = ctrl('N'); + ekey[rc].rval = 11; + ekey[rc].name = "^N"; + ekey[rc++].label = N_("Next Collection"); + } + + ekey[rc].ch = ctrl('W'); + ekey[rc].rval = 17; + ekey[rc].name = "^W"; + ekey[rc++].label = N_("INBOX"); + + if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ + ekey[rc].ch = TAB; + ekey[rc].rval = 12; + ekey[rc].name = "TAB"; + ekey[rc++].label = N_("Complete"); + } + + if(F_ON(F_ENABLE_SUB_LISTS, ps_global)){ + ekey[rc].ch = ctrl('X'); + ekey[rc].rval = 14; + ekey[rc].name = "^X"; + ekey[rc++].label = N_("ListMatches"); + } + + if(ps_global->context_list->next && F_ON(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ + ekey[rc].ch = KEY_UP; + ekey[rc].rval = 10; + ekey[rc].name = ""; + ekey[rc++].label = ""; + + ekey[rc].ch = KEY_DOWN; + ekey[rc].rval = 11; + ekey[rc].name = ""; + ekey[rc++].label = ""; + } + else if(F_OFF(F_DISABLE_SAVE_INPUT_HISTORY, ps_global)){ + ekey[rc].ch = KEY_UP; + ekey[rc].rval = 30; + ekey[rc].name = ""; + ku = rc; + ekey[rc++].label = ""; + + ekey[rc].ch = KEY_DOWN; + ekey[rc].rval = 31; + ekey[rc].name = ""; + ekey[rc++].label = ""; + } + + ekey[rc].ch = -1; + + while(!done) { + /* + * Figure out next default value for this context. The idea + * is that in each context the last folder opened is cached. + * It's up to pick it out and display it. This is fine + * and dandy if we've currently got the inbox open, BUT + * if not, make the inbox the default the first time thru. + */ + if(!inbox){ + last_folder = ps_global->inbox_name; + inbox = 1; /* pretend we're in inbox from here on out */ + } + else + last_folder = (ps_global->last_unambig_folder[0]) + ? ps_global->last_unambig_folder + : ((tc->last_folder[0]) ? tc->last_folder : NULL); + + if(last_folder){ + unsigned char *fname = folder_name_decoded((unsigned char *)last_folder); + snprintf(expanded, sizeof(expanded), " [%.*s]", sizeof(expanded)-5, + fname ? (char *) fname : last_folder); + if(fname) fs_give((void **)&fname); + } + else + *expanded = '\0'; + + expanded[sizeof(expanded)-1] = '\0'; + + /* only show collection number if more than one available */ + if(ps_global->context_list->next) + snprintf(prompt, sizeof(prompt), "GOTO %s in <%s> %.*s%s: ", + NEWS_TEST(tc) ? "news group" : "folder", + tc->nickname, sizeof(prompt)-50, expanded, + *expanded ? " " : ""); + else + snprintf(prompt, sizeof(prompt), "GOTO folder %.*s%s: ", sizeof(prompt)-20, expanded, + *expanded ? " " : ""); + + prompt[sizeof(prompt)-1] = '\0'; + + if(utf8_width(prompt) > MAXPROMPT){ + if(ps_global->context_list->next) + snprintf(prompt, sizeof(prompt), "GOTO <%s> %.*s%s: ", + tc->nickname, sizeof(prompt)-50, expanded, + *expanded ? " " : ""); + else + snprintf(prompt, sizeof(prompt), "GOTO %.*s%s: ", sizeof(prompt)-20, expanded, + *expanded ? " " : ""); + + prompt[sizeof(prompt)-1] = '\0'; + + if(utf8_width(prompt) > MAXPROMPT){ + if(ps_global->context_list->next) + snprintf(prompt, sizeof(prompt), "<%s> %.*s%s: ", + tc->nickname, sizeof(prompt)-50, expanded, + *expanded ? " " : ""); + else + snprintf(prompt, sizeof(prompt), "%.*s%s: ", sizeof(prompt)-20, expanded, + *expanded ? " " : ""); + + prompt[sizeof(prompt)-1] = '\0'; + } + } + + if(ku >= 0){ + if(items_in_hist(history) > 1){ + ekey[ku].name = HISTORY_UP_KEYNAME; + ekey[ku].label = HISTORY_KEYLABEL; + ekey[ku+1].name = HISTORY_DOWN_KEYNAME; + ekey[ku+1].label = HISTORY_KEYLABEL; + } + else{ + ekey[ku].name = ""; + ekey[ku].label = ""; + ekey[ku+1].name = ""; + ekey[ku+1].label = ""; + } + } + + flags = OE_APPEND_CURRENT; + rc = optionally_enter(newfolder, qline, 0, sizeof(newfolder), + prompt, ekey, help, &flags); + + ps_global->mangled_footer = 1; + + switch(rc){ + case -1 : /* o_e says error! */ + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("Error reading folder name")); + return(NULL); + + case 0 : /* o_e says normal entry */ + removing_trailing_white_space(newfolder); + removing_leading_white_space(newfolder); + + if(*newfolder){ + char *name, *fullname = NULL; + int exists, breakout = 0; + + save_hist(history, newfolder, 0, tc); + + if(!(name = folder_is_nick(newfolder, FOLDERS(tc), + FN_WHOLE_NAME))) + name = newfolder; + + if(update_folder_spec(expanded, sizeof(expanded), name)){ + strncpy(name = newfolder, expanded, sizeof(newfolder)); + newfolder[sizeof(newfolder)-1] = '\0'; + } + + exists = folder_name_exists(tc, name, &fullname); + + if(fullname){ + strncpy(name = newfolder, fullname, sizeof(newfolder)); + newfolder[sizeof(newfolder)-1] = '\0'; + fs_give((void **) &fullname); + breakout = TRUE; + } + + /* + * if we know the things a folder, open it. + * else if we know its a directory, visit it. + * else we're not sure (it either doesn't really + * exist or its unLISTable) so try opening it anyway + */ + if(exists & FEX_ISFILE){ + done++; + break; + } + else if((exists & FEX_ISDIR)){ + if(breakout){ + CONTEXT_S *fake_context; + char tmp[MAILTMPLEN]; + size_t l; + + strncpy(tmp, name, sizeof(tmp)); + tmp[sizeof(tmp)-2-1] = '\0'; + if(tmp[(l = strlen(tmp)) - 1] != tc->dir->delim){ + if(l < sizeof(tmp)){ + tmp[l] = tc->dir->delim; + strncpy(&tmp[l+1], "[]", sizeof(tmp)-(l+1)); + } + } + else + strncat(tmp, "[]", sizeof(tmp)-strlen(tmp)-1); + + tmp[sizeof(tmp)-1] = '\0'; + + fake_context = new_context(tmp, 0); + newfolder[0] = '\0'; + done = display_folder_list(&fake_context, newfolder, + 1, folders_for_goto); + free_context(&fake_context); + break; + } + else if(!(tc->use & CNTXT_INCMNG)){ + done = display_folder_list(&tc, newfolder, + 1, folders_for_goto); + break; + } + } + else if((exists & FEX_ERROR)){ + q_status_message1(SM_ORDER, 0, 3, + _("Problem accessing folder \"%s\""), + newfolder); + return(NULL); + } + else{ + done++; + break; + } + + if(exists == FEX_ERROR) + q_status_message1(SM_ORDER, 0, 3, + _("Problem accessing folder \"%s\""), + newfolder); + else if(tc->use & CNTXT_INCMNG) + q_status_message1(SM_ORDER, 0, 3, + _("Can't find Incoming Folder: %s"), + newfolder); + else if(context_isambig(newfolder)) + q_status_message2(SM_ORDER, 0, 3, + _("Can't find folder \"%s\" in %s"), + newfolder, (void *) tc->nickname); + else + q_status_message1(SM_ORDER, 0, 3, + _("Can't find folder \"%s\""), + newfolder); + + return(NULL); + } + else if(last_folder){ + if(ps_global->goto_default_rule == GOTO_FIRST_CLCTN_DEF_INBOX + && !strucmp(last_folder, ps_global->inbox_name) + && tc == ((ps_global->context_list->use & CNTXT_INCMNG) + ? ps_global->context_list->next : ps_global->context_list)){ + if(notrealinbox) + (*notrealinbox) = 0; + + tc = ps_global->context_list; + } + + strncpy(newfolder, last_folder, sizeof(newfolder)); + newfolder[sizeof(newfolder)-1] = '\0'; + save_hist(history, newfolder, 0, tc); + done++; + break; + } + /* fall thru like they cancelled */ + + case 1 : /* o_e says user cancel */ + cmd_cancelled("Open folder"); + return(NULL); + + case 2 : /* o_e says user wants list */ + r = display_folder_list(&tc, newfolder, 0, folders_for_goto); + if(r) + done++; + + break; + + case 3 : /* o_e says user wants help */ + help = help == NO_HELP ? h_oe_broach : NO_HELP; + break; + + case 4 : /* redraw */ + break; + + case 10 : /* Previous collection */ + tc2 = ps_global->context_list; + while(tc2->next && tc2->next != tc) + tc2 = tc2->next; + + tc = tc2; + break; + + case 11 : /* Next collection */ + tc = (tc->next) ? tc->next : ps_global->context_list; + break; + + case 12 : /* file name completion */ + if(!folder_complete(tc, newfolder, sizeof(newfolder), &n)){ + if(n && last_rc == 12 && !(flags & OE_USER_MODIFIED)){ + r = display_folder_list(&tc, newfolder, 1,folders_for_goto); + if(r) + done++; /* bingo! */ + else + rc = 0; /* burn last_rc */ + } + else + Writechar(BELL, 0); + } + + break; + + case 14 : /* file name completion */ + r = display_folder_list(&tc, newfolder, 2, folders_for_goto); + if(r) + done++; /* bingo! */ + else + rc = 0; /* burn last_rc */ + + break; + + case 17 : /* GoTo INBOX */ + done++; + strncpy(newfolder, ps_global->inbox_name, sizeof(newfolder)-1); + newfolder[sizeof(newfolder)-1] = '\0'; + if(notrealinbox) + (*notrealinbox) = 0; + + tc = ps_global->context_list; + save_hist(history, newfolder, 0, tc); + + break; + + case 30 : + if((p = get_prev_hist(history, newfolder, 0, tc)) != NULL){ + strncpy(newfolder, p, sizeof(newfolder)); + newfolder[sizeof(newfolder)-1] = '\0'; + if(history->hist[history->curindex]) + tc = history->hist[history->curindex]->cntxt; + } + else + Writechar(BELL, 0); + + break; + + case 31 : + if((p = get_next_hist(history, newfolder, 0, tc)) != NULL){ + strncpy(newfolder, p, sizeof(newfolder)); + newfolder[sizeof(newfolder)-1] = '\0'; + if(history->hist[history->curindex]) + tc = history->hist[history->curindex]->cntxt; + } + else + Writechar(BELL, 0); + + break; + + default : + panic("Unhandled case"); + break; + } + + last_rc = rc; + } + + dprint((2, "broach folder, name entered \"%s\"\n", + newfolder ? newfolder : "?")); + + /*-- Just check that we can expand this. It gets done for real later --*/ + strncpy(expanded, newfolder, sizeof(expanded)); + expanded[sizeof(expanded)-1] = '\0'; + + if(!expand_foldername(expanded, sizeof(expanded))) { + dprint((1, "Error: Failed on expansion of filename %s (do_broach)\n", + expanded ? expanded : "?")); + return(NULL); + } + + *context = tc; + return(newfolder); +} + + +/*---------------------------------------------------------------------- + Check to see if user wants to reopen dead stream. + + Args: ps -- + reopenp -- + + Result: 1 if the folder was successfully updatedn + 0 if not necessary + + ----*/ +int +ask_mailbox_reopen(struct pine *ps, int *reopenp) +{ + if(((ps->mail_stream->dtb + && ((ps->mail_stream->dtb->flags & DR_NONEWMAIL) + || (ps->mail_stream->rdonly + && ps->mail_stream->dtb->flags & DR_NONEWMAILRONLY))) + && (ps->reopen_rule == REOPEN_ASK_ASK_Y + || ps->reopen_rule == REOPEN_ASK_ASK_N + || ps->reopen_rule == REOPEN_ASK_NO_Y + || ps->reopen_rule == REOPEN_ASK_NO_N)) + || ((ps->mail_stream->dtb + && ps->mail_stream->rdonly + && !(ps->mail_stream->dtb->flags & DR_LOCAL)) + && (ps->reopen_rule == REOPEN_YES_ASK_Y + || ps->reopen_rule == REOPEN_YES_ASK_N + || ps->reopen_rule == REOPEN_ASK_ASK_Y + || ps->reopen_rule == REOPEN_ASK_ASK_N))){ + int deefault; + + switch(ps->reopen_rule){ + case REOPEN_YES_ASK_Y: + case REOPEN_ASK_ASK_Y: + case REOPEN_ASK_NO_Y: + deefault = 'y'; + break; + + default: + deefault = 'n'; + break; + } + + switch(want_to("Re-open folder to check for new messages", deefault, + 'x', h_reopen_folder, WT_NORM)){ + case 'y': + (*reopenp)++; + break; + + case 'x': + return(-1); + } + } + + return(0); +} + + + +/*---------------------------------------------------------------------- + Check to see if user input is in form of old c-client mailbox speck + + Args: old -- + new -- + + Result: 1 if the folder was successfully updatedn + 0 if not necessary + + ----*/ +int +update_folder_spec(char *new, size_t newlen, char *old) +{ + char *p, *orignew; + int nntp = 0; + + orignew = new; + if(*(p = old) == '*') /* old form? */ + old++; + + if(*old == '{') /* copy host spec */ + do + switch(*new = *old++){ + case '\0' : + return(FALSE); + + case '/' : + if(!struncmp(old, "nntp", 4)) + nntp++; + + break; + + default : + break; + } + while(*new++ != '}' && (new-orignew) < newlen-1); + + if((*p == '*' && *old) || ((*old == '*') ? *++old : 0)){ + /* + * OK, some heuristics here. If it looks like a newsgroup + * then we plunk it into the #news namespace else we + * assume that they're trying to get at a #public folder... + */ + for(p = old; + *p && (isalnum((unsigned char) *p) || strindex(".-", *p)); + p++) + ; + + sstrncpy(&new, (*p && !nntp) ? "#public/" : "#news.", newlen-(new-orignew)); + strncpy(new, old, newlen-(new-orignew)); + return(TRUE); + } + + orignew[newlen-1] = '\0'; + + return(FALSE); +} + + +/*---------------------------------------------------------------------- + Open the requested folder in the requested context + + Args: state -- usual pine state struct + newfolder -- folder to open + new_context -- folder context might live in + stream -- candidate for recycling + + Result: New folder open or not (if error), and we're set to + enter the index screen. + ----*/ +void +visit_folder(struct pine *state, char *newfolder, CONTEXT_S *new_context, + MAILSTREAM *stream, long unsigned int flags) +{ + dprint((9, "visit_folder(%s, %s)\n", + newfolder ? newfolder : "?", + (new_context && new_context->context) + ? new_context->context : "(NULL)")); + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + if(do_broach_folder(newfolder, new_context, stream ? &stream : NULL, + flags) >= 0 + || !sp_flagged(state->mail_stream, SP_LOCKED)) + state->next_screen = mail_index_screen; + else + state->next_screen = folder_screen; +} + + +/*---------------------------------------------------------------------- + Move read messages from folder if listed in archive + + Args: + + ----*/ +int +read_msg_prompt(long int n, char *f) +{ + char buf[MAX_SCREEN_COLS+1]; + + snprintf(buf, sizeof(buf), "Save the %ld read message%s in \"%s\"", n, plural(n), f); + buf[sizeof(buf)-1] = '\0'; + return(want_to(buf, 'y', 0, NO_HELP, WT_NORM) == 'y'); +} + + +/*---------------------------------------------------------------------- + Print current message[s] or folder index + + Args: state -- pointer to struct holding a bunch of pine state + msgmap -- table mapping msg nums to c-client sequence nums + aopt -- aggregate options + in_index -- boolean indicating we're called from Index Screen + + Filters the original header and sends stuff to printer + ---*/ +int +cmd_print(struct pine *state, MSGNO_S *msgmap, int aopt, CmdWhere in_index) +{ + char prompt[250]; + long i, msgs, rawno; + int next = 0, do_index = 0, rv = 0; + ENVELOPE *e; + BODY *b; + MESSAGECACHE *mc; + + if(MCMD_ISAGG(aopt) && !pseudo_selected(state->mail_stream, msgmap)) + return rv; + + msgs = mn_total_cur(msgmap); + + if((in_index != View) && F_ON(F_PRINT_INDEX, state)){ + char m[10]; + int ans; + static ESCKEY_S prt_opts[] = { + {'i', 'i', "I", N_("Index")}, + {'m', 'm', "M", NULL}, + {-1, 0, NULL, NULL}}; + + if(in_index == ThrdIndx){ + /* TRANSLATORS: This is a question, Print Index ? */ + if(want_to(_("Print Index"), 'y', 'x', NO_HELP, WT_NORM) == 'y') + ans = 'i'; + else + ans = 'x'; + } + else{ + snprintf(m, sizeof(m), "Message%s", (msgs>1L) ? "s" : ""); + m[sizeof(m)-1] = '\0'; + prt_opts[1].label = m; + snprintf(prompt, sizeof(prompt), "Print %sFolder Index or %s %s? ", + (aopt & MCMD_AGG_2) ? "thread " : MCMD_ISAGG(aopt) ? "selected " : "", + (aopt & MCMD_AGG_2) ? "thread" : MCMD_ISAGG(aopt) ? "selected" : "current", m); + prompt[sizeof(prompt)-1] = '\0'; + + ans = radio_buttons(prompt, -FOOTER_ROWS(state), prt_opts, 'm', 'x', + NO_HELP, RB_NORM|RB_SEQ_SENSITIVE); + } + + switch(ans){ + case 'x' : + cmd_cancelled("Print"); + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + return rv; + + case 'i': + do_index = 1; + break; + + default : + case 'm': + break; + } + } + + if(do_index) + snprintf(prompt, sizeof(prompt), "%sFolder Index", + (aopt & MCMD_AGG_2) ? "Thread " : MCMD_ISAGG(aopt) ? "Selected " : ""); + else if(msgs > 1L) + snprintf(prompt, sizeof(prompt), "%s messages", long2string(msgs)); + else + snprintf(prompt, sizeof(prompt), "Message %s", long2string(mn_get_cur(msgmap))); + + prompt[sizeof(prompt)-1] = '\0'; + + if(open_printer(prompt) < 0){ + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + return rv; + } + + if(do_index){ + TITLE_S *tc; + + tc = format_titlebar(); + + /* Print titlebar... */ + print_text1("%s\n\n", tc ? tc->titlebar_line : ""); + /* then all the index members... */ + if(!print_index(state, msgmap, MCMD_ISAGG(aopt))) + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("Error printing folder index")); + else + rv++; + } + else{ + rv++; + for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap), next++){ + if(next && F_ON(F_AGG_PRINT_FF, state)) + if(!print_char(FORMFEED)){ + rv = 0; + break; + } + + if(!(state->mail_stream + && (rawno = mn_m2raw(msgmap, i)) > 0L + && rawno <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, rawno)) + && mc->valid)) + mc = NULL; + + if(!(e=pine_mail_fetchstructure(state->mail_stream, + mn_m2raw(msgmap,i), + &b)) + || (F_ON(F_FROM_DELIM_IN_PRINT, ps_global) + && !bezerk_delimiter(e, mc, print_char, next)) + || !format_message(mn_m2raw(msgmap, mn_get_cur(msgmap)), + e, b, NULL, FM_NEW_MESS | FM_NOINDENT, + print_char)){ + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("Error printing message")); + rv = 0; + break; + } + } + } + + close_printer(); + + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + return rv; +} + + +/*---------------------------------------------------------------------- + Pipe message text + + Args: state -- various pine state bits + msgmap -- Message number mapping table + aopt -- option flags + + Filters the original header and sends stuff to specified command + ---*/ +int +cmd_pipe(struct pine *state, MSGNO_S *msgmap, int aopt) +{ + ENVELOPE *e; + MESSAGECACHE *mc; + BODY *b; + PIPE_S *syspipe; + char *resultfilename = NULL, prompt[80], *p; + int done = 0, rv = 0; + gf_io_t pc; + int fourlabel = -1, j = 0, next = 0, ku; + int pipe_rv; /* rv of proc to separate from close_system_pipe rv */ + long i, rawno; + unsigned flagsforhist = 1; /* raw=8/delimit=4/newpipe=2/capture=1 */ + static HISTORY_S *history = NULL; + int capture = 1, raw = 0, delimit = 0, newpipe = 0; + char pipe_command[MAXPATH]; + ESCKEY_S pipe_opt[8]; + + if(ps_global->restricted){ + q_status_message(SM_ORDER | SM_DING, 0, 4, + "Alpine demo can't pipe messages"); + return rv; + } + else if(!any_messages(msgmap, NULL, "to Pipe")) + return rv; + + pipe_command[0] = '\0'; + init_hist(&history, HISTSIZE); + flagsforhist = (raw ? 0x8 : 0) + + (delimit ? 0x4 : 0) + + (newpipe ? 0x2 : 0) + + (capture ? 0x1 : 0); + if((p = get_prev_hist(history, "", flagsforhist, NULL)) != NULL){ + strncpy(pipe_command, p, sizeof(pipe_command)); + pipe_command[sizeof(pipe_command)-1] = '\0'; + if(history->hist[history->curindex]){ + flagsforhist = history->hist[history->curindex]->flags; + raw = (flagsforhist & 0x8) ? 1 : 0; + delimit = (flagsforhist & 0x4) ? 1 : 0; + newpipe = (flagsforhist & 0x2) ? 1 : 0; + capture = (flagsforhist & 0x1) ? 1 : 0; + } + } + + pipe_opt[j].ch = 0; + pipe_opt[j].rval = 0; + pipe_opt[j].name = ""; + pipe_opt[j++].label = ""; + + pipe_opt[j].ch = ctrl('W'); + pipe_opt[j].rval = 10; + pipe_opt[j].name = "^W"; + pipe_opt[j++].label = NULL; + + pipe_opt[j].ch = ctrl('Y'); + pipe_opt[j].rval = 11; + pipe_opt[j].name = "^Y"; + pipe_opt[j++].label = NULL; + + pipe_opt[j].ch = ctrl('R'); + pipe_opt[j].rval = 12; + pipe_opt[j].name = "^R"; + pipe_opt[j++].label = NULL; + + if(MCMD_ISAGG(aopt)){ + if(!pseudo_selected(state->mail_stream, msgmap)) + return rv; + else{ + fourlabel = j; + pipe_opt[j].ch = ctrl('T'); + pipe_opt[j].rval = 13; + pipe_opt[j].name = "^T"; + pipe_opt[j++].label = NULL; + } + } + + pipe_opt[j].ch = KEY_UP; + pipe_opt[j].rval = 30; + pipe_opt[j].name = ""; + ku = j; + pipe_opt[j++].label = ""; + + pipe_opt[j].ch = KEY_DOWN; + pipe_opt[j].rval = 31; + pipe_opt[j].name = ""; + pipe_opt[j++].label = ""; + + pipe_opt[j].ch = -1; + + while (!done) { + int flags; + + snprintf(prompt, sizeof(prompt), "Pipe %smessage%s%s to %s%s%s%s%s%s%s: ", + raw ? "RAW " : "", + MCMD_ISAGG(aopt) ? "s" : " ", + MCMD_ISAGG(aopt) ? "" : comatose(mn_get_cur(msgmap)), + (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? "(" : "", + capture ? "" : "uncaptured", + (!capture && delimit) ? "," : "", + delimit ? "delimited" : "", + ((!capture || delimit) && newpipe && MCMD_ISAGG(aopt)) ? "," : "", + (newpipe && MCMD_ISAGG(aopt)) ? "new pipe" : "", + (!capture || delimit || (newpipe && MCMD_ISAGG(aopt))) ? ") " : ""); + prompt[sizeof(prompt)-1] = '\0'; + pipe_opt[1].label = raw ? N_("Shown Text") : N_("Raw Text"); + pipe_opt[2].label = capture ? N_("Free Output") : N_("Capture Output"); + pipe_opt[3].label = delimit ? N_("No Delimiter") : N_("With Delimiter"); + if(fourlabel > 0) + pipe_opt[fourlabel].label = newpipe ? N_("To Same Pipe") : N_("To Individual Pipes"); + + + /* + * 2 is really 1 because there will be one real entry and + * one entry of "" because of the get_prev_hist above. + */ + if(items_in_hist(history) > 2){ + pipe_opt[ku].name = HISTORY_UP_KEYNAME; + pipe_opt[ku].label = HISTORY_KEYLABEL; + pipe_opt[ku+1].name = HISTORY_DOWN_KEYNAME; + pipe_opt[ku+1].label = HISTORY_KEYLABEL; + } + else{ + pipe_opt[ku].name = ""; + pipe_opt[ku].label = ""; + pipe_opt[ku+1].name = ""; + pipe_opt[ku+1].label = ""; + } + + flags = OE_APPEND_CURRENT | OE_SEQ_SENSITIVE; + switch(optionally_enter(pipe_command, -FOOTER_ROWS(state), 0, + sizeof(pipe_command), prompt, + pipe_opt, NO_HELP, &flags)){ + case -1 : + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Internal problem encountered")); + done++; + break; + + case 10 : /* flip raw bit */ + raw = !raw; + break; + + case 11 : /* flip capture bit */ + capture = !capture; + break; + + case 12 : /* flip delimit bit */ + delimit = !delimit; + break; + + case 13 : /* flip newpipe bit */ + newpipe = !newpipe; + break; + + case 30 : + flagsforhist = (raw ? 0x8 : 0) + + (delimit ? 0x4 : 0) + + (newpipe ? 0x2 : 0) + + (capture ? 0x1 : 0); + if((p = get_prev_hist(history, pipe_command, flagsforhist, NULL)) != NULL){ + strncpy(pipe_command, p, sizeof(pipe_command)); + pipe_command[sizeof(pipe_command)-1] = '\0'; + if(history->hist[history->curindex]){ + flagsforhist = history->hist[history->curindex]->flags; + raw = (flagsforhist & 0x8) ? 1 : 0; + delimit = (flagsforhist & 0x4) ? 1 : 0; + newpipe = (flagsforhist & 0x2) ? 1 : 0; + capture = (flagsforhist & 0x1) ? 1 : 0; + } + } + else + Writechar(BELL, 0); + + break; + + case 31 : + flagsforhist = (raw ? 0x8 : 0) + + (delimit ? 0x4 : 0) + + (newpipe ? 0x2 : 0) + + (capture ? 0x1 : 0); + if((p = get_next_hist(history, pipe_command, flagsforhist, NULL)) != NULL){ + strncpy(pipe_command, p, sizeof(pipe_command)); + pipe_command[sizeof(pipe_command)-1] = '\0'; + if(history->hist[history->curindex]){ + flagsforhist = history->hist[history->curindex]->flags; + raw = (flagsforhist & 0x8) ? 1 : 0; + delimit = (flagsforhist & 0x4) ? 1 : 0; + newpipe = (flagsforhist & 0x2) ? 1 : 0; + capture = (flagsforhist & 0x1) ? 1 : 0; + } + } + else + Writechar(BELL, 0); + + break; + + case 0 : + if(pipe_command[0]){ + + flagsforhist = (raw ? 0x8 : 0) + + (delimit ? 0x4 : 0) + + (newpipe ? 0x2 : 0) + + (capture ? 0x1 : 0); + save_hist(history, pipe_command, flagsforhist, NULL); + + flags = PIPE_USER | PIPE_WRITE | PIPE_STDERR; + flags |= (raw ? PIPE_RAW : 0); + if(!capture){ +#ifndef _WINDOWS + ClearScreen(); + fflush(stdout); + clear_cursor_pos(); + ps_global->mangled_screen = 1; + ps_global->in_init_seq = 1; +#endif + flags |= PIPE_RESET; + } + + if(!newpipe && !(syspipe = cmd_pipe_open(pipe_command, + (flags & PIPE_RESET) + ? NULL + : &resultfilename, + flags, &pc))) + done++; + + for(i = mn_first_cur(msgmap); + i > 0L && !done; + i = mn_next_cur(msgmap)){ + e = pine_mail_fetchstructure(ps_global->mail_stream, + mn_m2raw(msgmap, i), &b); + if(!(state->mail_stream + && (rawno = mn_m2raw(msgmap, i)) > 0L + && rawno <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, rawno)) + && mc->valid)) + mc = NULL; + + if((newpipe + && !(syspipe = cmd_pipe_open(pipe_command, + (flags & PIPE_RESET) + ? NULL + : &resultfilename, + flags, &pc))) + || (delimit && !bezerk_delimiter(e, mc, pc, next++))) + done++; + + if(!done){ + if(raw){ + char *pipe_err; + + prime_raw_pipe_getc(ps_global->mail_stream, + mn_m2raw(msgmap, i), -1L, 0L); + gf_filter_init(); + gf_link_filter(gf_nvtnl_local, NULL); + if((pipe_err = gf_pipe(raw_pipe_getc, pc)) != NULL){ + q_status_message1(SM_ORDER|SM_DING, + 3, 3, + _("Internal Error: %s"), + pipe_err); + done++; + } + } + else if(!format_message(mn_m2raw(msgmap, i), e, b, + NULL, FM_NEW_MESS | FM_NOWRAP, pc)) + done++; + } + + if(newpipe) + if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1) + done++; + } + + if(!capture) + ps_global->in_init_seq = 0; + + if(!newpipe) + if(close_system_pipe(&syspipe, &pipe_rv, pipe_callback) == -1) + done++; + if(done) /* say we had a problem */ + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("Error piping message")); + else if(resultfilename){ + rv++; + /* only display if no error */ + display_output_file(resultfilename, "PIPE MESSAGE", + NULL, DOF_EMPTY); + fs_give((void **)&resultfilename); + } + else{ + rv++; + q_status_message(SM_ORDER, 0, 2, _("Pipe command completed")); + } + + done++; + break; + } + /* else fall thru as if cancelled */ + + case 1 : + cmd_cancelled("Pipe command"); + done++; + break; + + case 3 : + helper(h_common_pipe, _("HELP FOR PIPE COMMAND"), HLPD_SIMPLE); + ps_global->mangled_screen = 1; + break; + + case 2 : /* no place to escape to */ + case 4 : /* can't suspend */ + default : + break; + } + } + + ps_global->mangled_footer = 1; + if(MCMD_ISAGG(aopt)) + restore_selected(msgmap); + + return rv; +} + + +/*---------------------------------------------------------------------- + Screen to offer list management commands contained in message + + Args: state -- pointer to struct holding a bunch of pine state + msgmap -- table mapping msg nums to c-client sequence nums + aopt -- aggregate options + + Result: + + NOTE: Inspired by contrib from Jeremy Blackman <loki@maison-otaku.net> + ----*/ +void +rfc2369_display(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno) +{ + int winner = 0; + char *h, *hdrs[MLCMD_COUNT + 1]; + long index_no = mn_raw2m(msgmap, msgno); + RFC2369_S data[MLCMD_COUNT]; + + /* for each header field */ + if((h = pine_fetchheader_lines(stream, msgno, NULL, rfc2369_hdrs(hdrs))) != NULL){ + memset(&data[0], 0, sizeof(RFC2369_S) * MLCMD_COUNT); + if(rfc2369_parse_fields(h, &data[0])){ + STORE_S *explain; + + if((explain = list_mgmt_text(data, index_no)) != NULL){ + list_mgmt_screen(explain); + ps_global->mangled_screen = 1; + so_give(&explain); + winner++; + } + } + + fs_give((void **) &h); + } + + if(!winner) + q_status_message1(SM_ORDER, 0, 3, + "Message %s contains no list management information", + comatose(index_no)); +} + + +STORE_S * +list_mgmt_text(RFC2369_S *data, long int msgno) +{ + STORE_S *store; + int i, j, n, fields = 0; + static char *rfc2369_intro1 = + "<HTML><HEAD></HEAD><BODY><H1>Mail List Commands</H1>Message "; + static char *rfc2369_intro2[] = { + N_(" has information associated with it "), + N_("that explains how to participate in an email list. An "), + N_("email list is represented by a single email address that "), + N_("users sharing a common interest can send messages to (known "), + N_("as posting) which are then redistributed to all members "), + N_("of the list (sometimes after review by a moderator)."), + N_("<P>List participation commands in this message include:"), + NULL + }; + + if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){ + + /* Insert introductory text */ + so_puts(store, rfc2369_intro1); + + so_puts(store, comatose(msgno)); + + for(i = 0; rfc2369_intro2[i]; i++) + so_puts(store, _(rfc2369_intro2[i])); + + so_puts(store, "<P>"); + for(i = 0; i < MLCMD_COUNT; i++) + if(data[i].data[0].value + || data[i].data[0].comment + || data[i].data[0].error){ + if(!fields++) + so_puts(store, "<UL>"); + + so_puts(store, "<LI>"); + so_puts(store, + (n = (data[i].data[1].value || data[i].data[1].comment)) + ? "Methods to " + : "A method to "); + + so_puts(store, data[i].field.description); + so_puts(store, ". "); + + if(n) + so_puts(store, "<OL>"); + + for(j = 0; + j < MLCMD_MAXDATA + && (data[i].data[j].comment + || data[i].data[j].value + || data[i].data[j].error); + j++){ + + so_puts(store, n ? "<P><LI>" : "<P>"); + + if(data[i].data[j].comment){ + so_puts(store, + _("With the provided comment:<P><BLOCKQUOTE>")); + so_puts(store, data[i].data[j].comment); + so_puts(store, "</BLOCKQUOTE><P>"); + } + + if(data[i].data[j].value){ + if(i == MLCMD_POST + && !strucmp(data[i].data[j].value, "NO")){ + so_puts(store, + _("Posting is <EM>not</EM> allowed on this list")); + } + else{ + so_puts(store, "Select <A HREF=\""); + so_puts(store, data[i].data[j].value); + so_puts(store, "\">HERE</A> to "); + so_puts(store, (data[i].field.action) + ? data[i].field.action + : "try it"); + } + + so_puts(store, "."); + } + + if(data[i].data[j].error){ + so_puts(store, "<P>Unfortunately, Alpine can not offer"); + so_puts(store, " to take direct action based upon it"); + so_puts(store, " because it was improperly formatted."); + so_puts(store, " The unrecognized data associated with"); + so_puts(store, " the \""); + so_puts(store, data[i].field.name); + so_puts(store, "\" header field was:<P><BLOCKQUOTE>"); + so_puts(store, data[i].data[j].error); + so_puts(store, "</BLOCKQUOTE>"); + } + + so_puts(store, "<P>"); + } + + if(n) + so_puts(store, "</OL>"); + } + + if(fields) + so_puts(store, "</UL>"); + + so_puts(store, "</BODY></HTML>"); + } + + return(store); +} + + +void +list_mgmt_screen(STORE_S *html) +{ + int cmd = MC_NONE; + long offset = 0L; + char *error = NULL; + STORE_S *store; + HANDLE_S *handles = NULL; + gf_io_t gc, pc; + + do{ + so_seek(html, 0L, 0); + gf_set_so_readc(&gc, html); + + init_handles(&handles); + + if((store = so_get(CharStar, NULL, EDIT_ACCESS)) != NULL){ + gf_set_so_writec(&pc, store); + gf_filter_init(); + + gf_link_filter(gf_html2plain, + gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols, + non_messageview_margin(), &handles, NULL, 0)); + + error = gf_pipe(gc, pc); + + gf_clear_so_writec(store); + + if(!error){ + SCROLL_S sargs; + + memset(&sargs, 0, sizeof(SCROLL_S)); + sargs.text.text = so_text(store); + sargs.text.src = CharStar; + sargs.text.desc = "list commands"; + sargs.text.handles = handles; + if(offset){ + sargs.start.on = Offset; + sargs.start.loc.offset = offset; + } + + sargs.bar.title = _("MAIL LIST COMMANDS"); + sargs.bar.style = MessageNumber; + sargs.resize_exit = 1; + sargs.help.text = h_special_list_commands; + sargs.help.title = _("HELP FOR LIST COMMANDS"); + sargs.keys.menu = &listmgr_keymenu; + setbitmap(sargs.keys.bitmap); + if(!handles){ + clrbitn(LM_TRY_KEY, sargs.keys.bitmap); + clrbitn(LM_PREV_KEY, sargs.keys.bitmap); + clrbitn(LM_NEXT_KEY, sargs.keys.bitmap); + } + + cmd = scrolltool(&sargs); + offset = sargs.start.loc.offset; + } + + so_give(&store); + } + + free_handles(&handles); + gf_clear_so_readc(html); + } + while(cmd == MC_RESIZE); +} + + +/*---------------------------------------------------------------------- + Prompt the user for the type of select desired + + NOTE: any and all functions that successfully exit the second + switch() statement below (currently "select_*() functions"), + *MUST* update the folder's MESSAGECACHE element's "searched" + bits to reflect the search result. Functions using + mail_search() get this for free, the others must update 'em + by hand. + + Returns -1 if canceled without changing selection + 0 if selection may have changed + ----*/ +int +aggregate_select(struct pine *state, MSGNO_S *msgmap, int q_line, CmdWhere in_index) +{ + long i, diff, old_tot, msgno, raw; + int q = 0, rv = 0, narrow = 0, hidden, ret = -1; + ESCKEY_S *sel_opts; + MESSAGECACHE *mc; + SEARCHSET *limitsrch = NULL; + PINETHRD_S *thrd; + extern MAILSTREAM *mm_search_stream; + extern long mm_search_count; + + hidden = any_lflagged(msgmap, MN_HIDE) > 0L; + mm_search_stream = state->mail_stream; + mm_search_count = 0L; + + sel_opts = THRD_INDX() ? sel_opts4 : sel_opts2; + if(THREADING()){ + sel_opts[SEL_OPTS_THREAD].ch = SEL_OPTS_THREAD_CH; + } + else{ + sel_opts[SEL_OPTS_THREAD].ch = -1; + } + + if((old_tot = any_lflagged(msgmap, MN_SLCT)) != 0){ + if(THRD_INDX()){ + i = 0; + thrd = fetch_thread(state->mail_stream, + mn_m2raw(msgmap, mn_get_cur(msgmap))); + /* check if whole thread is selected or not */ + if(thrd && + count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_SLCT) + == + count_lflags_in_thread(state->mail_stream,thrd,msgmap,MN_NONE)) + i = 1; + + sel_opts1[1].label = i ? N_("unselect Curthrd") : N_("select Curthrd"); + } + else{ + i = get_lflag(state->mail_stream, msgmap, mn_get_cur(msgmap), + MN_SLCT); + sel_opts1[1].label = i ? N_("unselect Cur") : N_("select Cur"); + } + + sel_opts += 2; /* disable extra options */ + switch(q = radio_buttons(_(sel_pmt1), q_line, sel_opts1, 'c', 'x', NO_HELP, + RB_NORM)){ + case 'f' : /* flip selection */ + msgno = 0L; + for(i = 1L; i <= mn_get_total(msgmap); i++){ + ret = 0; + q = !get_lflag(state->mail_stream, msgmap, i, MN_SLCT); + set_lflag(state->mail_stream, msgmap, i, MN_SLCT, q); + if(hidden){ + set_lflag(state->mail_stream, msgmap, i, MN_HIDE, !q); + if(!msgno && q) + mn_reset_cur(msgmap, msgno = i); + } + } + + return(ret); + + case 'n' : /* narrow selection */ + narrow++; + case 'b' : /* broaden selection */ + q = 0; /* offer criteria prompt */ + break; + + case 'c' : /* Un/Select Current */ + case 'a' : /* Unselect All */ + case 'x' : /* cancel */ + break; + + default : + q_status_message(SM_ORDER | SM_DING, 3, 3, + "Unsupported Select option"); + return(ret); + } + } + + if(!q){ + while(1){ + q = radio_buttons(sel_pmt2, q_line, sel_opts, 'c', 'x', + NO_HELP, RB_NORM|RB_RET_HELP); + + if(q == 3){ + helper(h_index_cmd_select, _("HELP FOR SELECT"), HLPD_SIMPLE); + ps_global->mangled_screen = 1; + } + else + break; + } + } + + /* + * The purpose of this is to add the appropriate searchset to the + * search so that the search can be limited to only looking at what + * it needs to look at. That is, if we are narrowing then we only need + * to look at messages which are already selected, and if we are + * broadening, then we only need to look at messages which are not + * yet selected. This routine will work whether or not + * limiting_searchset properly limits the search set. In particular, + * the searchset returned by limiting_searchset may include messages + * which really shouldn't be included. We do that because a too-large + * searchset will break some IMAP servers. It is even possible that it + * becomes inefficient to send the whole set. If the select function + * frees limitsrch, it should be sure to set it to NULL so we won't + * try freeing it again here. + */ + limitsrch = limiting_searchset(state->mail_stream, narrow); + + /* + * NOTE: See note about MESSAGECACHE "searched" bits above! + */ + switch(q){ + case 'x': /* cancel */ + cmd_cancelled("Select command"); + return(ret); + + case 'c' : /* select/unselect current */ + (void) select_by_current(state, msgmap, in_index); + ret = 0; + return(ret); + + case 'a' : /* select/unselect all */ + msgno = any_lflagged(msgmap, MN_SLCT); + diff = (!msgno) ? mn_get_total(msgmap) : 0L; + ret = 0; + agg_select_all(state->mail_stream, msgmap, &diff, + any_lflagged(msgmap, MN_SLCT) <= 0L); + q_status_message4(SM_ORDER,0,2, + "%s%s message%s %sselected", + msgno ? "" : "All ", comatose(diff), + plural(diff), msgno ? "UN" : ""); + return(ret); + + case 'n' : /* Select by Number */ + ret = 0; + if(THRD_INDX()) + rv = select_by_thrd_number(state->mail_stream, msgmap, &limitsrch); + else + rv = select_by_number(state->mail_stream, msgmap, &limitsrch); + + break; + + case 'd' : /* Select by Date */ + ret = 0; + rv = select_by_date(state->mail_stream, msgmap, mn_get_cur(msgmap), + &limitsrch); + break; + + case 't' : /* Text */ + ret = 0; + rv = select_by_text(state->mail_stream, msgmap, mn_get_cur(msgmap), + &limitsrch); + break; + + case 'z' : /* Size */ + ret = 0; + rv = select_by_size(state->mail_stream, &limitsrch); + break; + + case 's' : /* Status */ + ret = 0; + rv = select_by_status(state->mail_stream, &limitsrch); + break; + + case 'k' : /* Keyword */ + ret = 0; + rv = select_by_keyword(state->mail_stream, &limitsrch); + break; + + case 'r' : /* Rule */ + ret = 0; + rv = select_by_rule(state->mail_stream, &limitsrch); + break; + + case 'h' : /* Thread */ + ret = 0; + rv = select_by_thread(state->mail_stream, msgmap, &limitsrch); + break; + + default : + q_status_message(SM_ORDER | SM_DING, 3, 3, + "Unsupported Select option"); + return(ret); + } + + if(limitsrch) + mail_free_searchset(&limitsrch); + + if(rv) /* bad return value.. */ + return(ret); /* error already displayed */ + + if(narrow) /* make sure something was selected */ + for(i = 1L; i <= mn_get_total(msgmap); i++) + if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream + && raw <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){ + if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)) + break; + else + mm_search_count--; + } + + diff = 0L; + if(mm_search_count){ + /* + * loop thru all the messages, adjusting local flag bits + * based on their "searched" bit... + */ + for(i = 1L, msgno = 0L; i <= mn_get_total(msgmap); i++) + if(narrow){ + /* turning OFF selectedness if the "searched" bit isn't lit. */ + if(get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){ + if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream + && raw <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, raw)) + && !mc->searched){ + diff--; + set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 0); + if(hidden) + set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 1); + } + /* adjust current message in case we unselect and hide it */ + else if(msgno < mn_get_cur(msgmap) + && (!THRD_INDX() + || !get_lflag(state->mail_stream, msgmap, + i, MN_CHID))) + msgno = i; + } + } + else if((raw = mn_m2raw(msgmap, i)) > 0L && state->mail_stream + && raw <= state->mail_stream->nmsgs + && (mc = mail_elt(state->mail_stream, raw)) && mc->searched){ + /* turn ON selectedness if "searched" bit is lit. */ + if(!get_lflag(state->mail_stream, msgmap, i, MN_SLCT)){ + diff++; + set_lflag(state->mail_stream, msgmap, i, MN_SLCT, 1); + if(hidden) + set_lflag(state->mail_stream, msgmap, i, MN_HIDE, 0); + } + } + + /* if we're zoomed and the current message was unselected */ + if(narrow && msgno + && get_lflag(state->mail_stream,msgmap,mn_get_cur(msgmap),MN_HIDE)) + mn_reset_cur(msgmap, msgno); + } + + if(!diff){ + if(narrow) + q_status_message4(SM_ORDER, 3, 3, + "%s. %s message%s remain%s selected.", + mm_search_count + ? "No change resulted" + : "No messages in intersection", + comatose(old_tot), plural(old_tot), + (old_tot == 1L) ? "s" : ""); + else if(old_tot) + q_status_message(SM_ORDER, 3, 3, + _("No change resulted. Matching messages already selected.")); + else + q_status_message1(SM_ORDER | SM_DING, 3, 3, + _("Select failed. No %smessages selected."), + old_tot ? _("additional ") : ""); + } + else if(old_tot){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, + "Select matched %ld message%s. %s %smessage%s %sselected.", + (diff > 0) ? diff : old_tot + diff, + plural((diff > 0) ? diff : old_tot + diff), + comatose((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff), + (diff > 0) ? "total " : "", + plural((diff > 0) ? any_lflagged(msgmap, MN_SLCT) : -diff), + (diff > 0) ? "" : "UN"); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + q_status_message(SM_ORDER, 3, 3, tmp_20k_buf); + } + else + q_status_message2(SM_ORDER, 3, 3, _("Select matched %s message%s!"), + comatose(diff), plural(diff)); + + return(ret); +} + + +/*---------------------------------------------------------------------- + Toggle the state of the current message + + Args: state -- pointer pine's state variables + msgmap -- message collection to operate on + in_index -- in the message index view + Returns: TRUE if current marked selected, FALSE otw + ----*/ +int +select_by_current(struct pine *state, MSGNO_S *msgmap, CmdWhere in_index) +{ + long cur; + int all_selected = 0; + unsigned long was, tot, rawno; + PINETHRD_S *thrd; + + cur = mn_get_cur(msgmap); + + if(THRD_INDX()){ + thrd = fetch_thread(state->mail_stream, mn_m2raw(msgmap, cur)); + if(!thrd) + return 0; + + was = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_SLCT); + tot = count_lflags_in_thread(state->mail_stream, thrd, msgmap, MN_NONE); + if(was == tot) + all_selected++; + + if(all_selected){ + set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 0); + if(any_lflagged(msgmap, MN_HIDE) > 0L){ + set_thread_lflags(state->mail_stream, thrd, msgmap, MN_HIDE, 1); + /* + * See if there's anything left to zoom on. If so, + * pick an adjacent one for highlighting, else make + * sure nothing is left hidden... + */ + if(any_lflagged(msgmap, MN_SLCT)){ + mn_inc_cur(state->mail_stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(state->mail_stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + if(mn_get_cur(msgmap) == cur) + mn_dec_cur(state->mail_stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(state->mail_stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + } + else /* clear all hidden flags */ + (void) unzoom_index(state, state->mail_stream, msgmap); + } + } + else + set_thread_lflags(state->mail_stream, thrd, msgmap, MN_SLCT, 1); + + q_status_message3(SM_ORDER, 0, 2, "%s message%s %sselected", + comatose(all_selected ? was : tot-was), + plural(all_selected ? was : tot-was), + all_selected ? "UN" : ""); + } + /* collapsed thread */ + else if(THREADING() + && ((rawno = mn_m2raw(msgmap, cur)) != 0L) + && ((thrd = fetch_thread(state->mail_stream, rawno)) != NULL) + && (thrd && thrd->next && get_lflag(state->mail_stream, NULL, rawno, MN_COLL))){ + /* + * This doesn't work quite the same as the colon command works, but + * it is arguably doing the correct thing. The difference is + * that aggregate_select will zoom after selecting back where it + * was called from, but selecting a thread with colon won't zoom. + * Maybe it makes sense to zoom after a select but not after a colon + * command even though they are very similar. + */ + thread_command(state, state->mail_stream, msgmap, ':', -FOOTER_ROWS(state)); + } + else{ + if((all_selected = + get_lflag(state->mail_stream, msgmap, cur, MN_SLCT)) != 0){ /* set? */ + set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 0); + if(any_lflagged(msgmap, MN_HIDE) > 0L){ + set_lflag(state->mail_stream, msgmap, cur, MN_HIDE, 1); + /* + * See if there's anything left to zoom on. If so, + * pick an adjacent one for highlighting, else make + * sure nothing is left hidden... + */ + if(any_lflagged(msgmap, MN_SLCT)){ + mn_inc_cur(state->mail_stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(state->mail_stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + if(mn_get_cur(msgmap) == cur) + mn_dec_cur(state->mail_stream, msgmap, + (in_index == View && THREADING() + && sp_viewing_a_thread(state->mail_stream)) + ? MH_THISTHD + : (in_index == View) + ? MH_ANYTHD : MH_NONE); + } + else /* clear all hidden flags */ + (void) unzoom_index(state, state->mail_stream, msgmap); + } + } + else + set_lflag(state->mail_stream, msgmap, cur, MN_SLCT, 1); + + q_status_message2(SM_ORDER, 0, 2, "Message %s %sselected", + long2string(cur), all_selected ? "UN" : ""); + } + + + return(!all_selected); +} + + +/*---------------------------------------------------------------------- + Prompt the user for the command to perform on selected messages + + Args: state -- pointer pine's state variables + msgmap -- message collection to operate on + q_line -- line on display to write prompts + Returns: 1 if the selected messages are suitably commanded, + 0 if the choice to pick the command was declined + + ----*/ +int +apply_command(struct pine *state, MAILSTREAM *stream, MSGNO_S *msgmap, + UCS preloadkeystroke, int flags, int q_line) +{ + int i = 8, /* number of static entries in sel_opts3 */ + rv = 0, + cmd, + we_cancel = 0, + agg = (flags & AC_FROM_THREAD) ? MCMD_AGG_2 : MCMD_AGG; + char prompt[80]; + + /* + * To do this "right", we really ought to have access to the keymenu + * here and change the typed command into a real command by running + * it through menu_command. Then the switch below would be against + * results from menu_command. If we did that we'd also pass the + * results of menu_command in as preloadkeystroke instead of passing + * the keystroke itself. But we don't have the keymenu handy, + * so we have to fake it. The only complication that we run into + * is that KEY_DEL is an escape sequence so we change a typed + * KEY_DEL esc seq into the letter D. + */ + + if(!preloadkeystroke){ + if(F_ON(F_ENABLE_FLAG,state)){ /* flag? */ + sel_opts3[i].ch = '*'; + sel_opts3[i].rval = '*'; + sel_opts3[i].name = "*"; + sel_opts3[i++].label = N_("Flag"); + } + + if(F_ON(F_ENABLE_PIPE,state)){ /* pipe? */ + sel_opts3[i].ch = '|'; + sel_opts3[i].rval = '|'; + sel_opts3[i].name = "|"; + sel_opts3[i++].label = N_("Pipe"); + } + + if(F_ON(F_ENABLE_BOUNCE,state)){ /* bounce? */ + sel_opts3[i].ch = 'b'; + sel_opts3[i].rval = 'b'; + sel_opts3[i].name = "B"; + sel_opts3[i++].label = N_("Bounce"); + } + + if(flags & AC_FROM_THREAD){ + if(flags & (AC_COLL | AC_EXPN)){ + sel_opts3[i].ch = '/'; + sel_opts3[i].rval = '/'; + sel_opts3[i].name = "/"; + sel_opts3[i++].label = (flags & AC_COLL) ? N_("Collapse") + : N_("Expand"); + } + + sel_opts3[i].ch = ';'; + sel_opts3[i].rval = ';'; + sel_opts3[i].name = ";"; + if(flags & AC_UNSEL) + sel_opts3[i++].label = N_("UnSelect"); + else + sel_opts3[i++].label = N_("Select"); + } + + if(F_ON(F_ENABLE_PRYNT, state)){ /* this one is invisible */ + sel_opts3[i].ch = 'y'; + sel_opts3[i].rval = '%'; + sel_opts3[i].name = ""; + sel_opts3[i++].label = ""; + } + + sel_opts3[i].ch = KEY_DEL; /* also invisible */ + sel_opts3[i].rval = 'd'; + sel_opts3[i].name = ""; + sel_opts3[i++].label = ""; + + sel_opts3[i].ch = -1; + + snprintf(prompt, sizeof(prompt), "%s command : ", + (flags & AC_FROM_THREAD) ? "THREAD" : "APPLY"); + prompt[sizeof(prompt)-1] = '\0'; + cmd = double_radio_buttons(prompt, q_line, sel_opts3, 'z', 'x', NO_HELP, + RB_SEQ_SENSITIVE); + if(isupper(cmd)) + cmd = tolower(cmd); + } + else{ + if(preloadkeystroke == KEY_DEL) + cmd = 'd'; + else{ + if(preloadkeystroke < 0x80 && isupper((int) preloadkeystroke)) + cmd = tolower((int) preloadkeystroke); + else + cmd = (int) preloadkeystroke; /* shouldn't happen */ + } + } + + switch(cmd){ + case 'd' : /* delete */ + we_cancel = busy_cue(NULL, NULL, 1); + rv = cmd_delete(state, msgmap, agg, NULL); /* don't advance or offer "TAB" */ + if(we_cancel) + cancel_busy_cue(0); + break; + + case 'u' : /* undelete */ + we_cancel = busy_cue(NULL, NULL, 1); + rv = cmd_undelete(state, msgmap, agg); + if(we_cancel) + cancel_busy_cue(0); + break; + + case 'r' : /* reply */ + rv = cmd_reply(state, msgmap, agg); + break; + + case 'f' : /* Forward */ + rv = cmd_forward(state, msgmap, agg); + break; + + case '%' : /* print */ + rv = cmd_print(state, msgmap, agg, MsgIndx); + break; + + case 't' : /* take address */ + rv = cmd_take_addr(state, msgmap, agg); + break; + + case 's' : /* save */ + rv = cmd_save(state, stream, msgmap, agg, MsgIndx); + break; + + case 'e' : /* export */ + rv = cmd_export(state, msgmap, q_line, agg); + break; + + case '|' : /* pipe */ + rv = cmd_pipe(state, msgmap, agg); + break; + + case '*' : /* flag */ + we_cancel = busy_cue(NULL, NULL, 1); + rv = cmd_flag(state, msgmap, agg); + if(we_cancel) + cancel_busy_cue(0); + break; + + case 'b' : /* bounce */ + rv = cmd_bounce(state, msgmap, agg); + break; + + case '/' : + collapse_or_expand(state, stream, msgmap, + F_ON(F_SLASH_COLL_ENTIRE, ps_global) + ? 0L + : mn_get_cur(msgmap)); + break; + + case ':' : + select_thread_stmp(state, stream, msgmap); + break; + + case 'x' : /* cancel */ + cmd_cancelled((flags & AC_FROM_THREAD) ? "Thread command" + : "Apply command"); + break; + + case 'z' : /* default */ + q_status_message(SM_INFO, 0, 2, + "Cancelled, there is no default command"); + break; + + default: + break; + } + + return(rv); +} + + +/* + * Select by message number ranges. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) +{ + int r, end; + long n1, n2, raw; + char number1[16], number2[16], numbers[80], *p, *t; + HelpType help; + MESSAGECACHE *mc; + + numbers[0] = '\0'; + ps_global->mangled_footer = 1; + help = NO_HELP; + while(1){ + int flags = OE_APPEND_CURRENT; + + r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, + sizeof(numbers), _(select_num), NULL, help, &flags); + if(r == 4) + continue; + + if(r == 3){ + help = (help == NO_HELP) ? h_select_by_num : NO_HELP; + continue; + } + + for(t = p = numbers; *p ; p++) /* strip whitespace */ + if(!isspace((unsigned char)*p)) + *t++ = *p; + + *t = '\0'; + + if(r == 1 || numbers[0] == '\0'){ + cmd_cancelled("Selection by number"); + return(1); + } + else + break; + } + + for(n1 = 1; n1 <= stream->nmsgs; n1++) + if((mc = mail_elt(stream, n1)) != NULL) + mc->searched = 0; /* clear searched bits */ + + for(p = numbers; *p ; p++){ + t = number1; + while(*p && isdigit((unsigned char)*p)) + *t++ = *p++; + + *t = '\0'; + + end = 0; + if(number1[0] == '\0'){ + if(*p == '-'){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid number range, missing number before \"-\": %s"), + numbers); + return(1); + } + else if(!strucmp("end", p)){ + end = 1; + p += strlen("end"); + } + else{ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid message number: %s"), numbers); + return(1); + } + } + + if(end) + n1 = mn_get_total(msgmap); + else if((n1 = atol(number1)) < 1L || n1 > mn_get_total(msgmap)){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("\"%s\" out of message number range"), + long2string(n1)); + return(1); + } + + t = number2; + if(*p == '-'){ + while(*++p && isdigit((unsigned char)*p)) + *t++ = *p; + + *t = '\0'; + + end = 0; + if(number2[0] == '\0'){ + if(!strucmp("end", p)){ + end = 1; + p += strlen("end"); + } + else{ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid number range, missing number after \"-\": %s"), + numbers); + return(1); + } + } + + if(end) + n2 = mn_get_total(msgmap); + else if((n2 = atol(number2)) < 1L || n2 > mn_get_total(msgmap)){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("\"%s\" out of message number range"), + long2string(n2)); + return(1); + } + + if(n2 <= n1){ + char t[20]; + + strncpy(t, long2string(n1), sizeof(t)); + t[sizeof(t)-1] = '\0'; + q_status_message2(SM_ORDER | SM_DING, 0, 2, + _("Invalid reverse message number range: %s-%s"), + t, long2string(n2)); + return(1); + } + + for(;n1 <= n2; n1++){ + raw = mn_m2raw(msgmap, n1); + if(raw > 0L + && (!(limitsrch && *limitsrch) + || in_searchset(*limitsrch, (unsigned long) raw))) + mm_searched(stream, raw); + } + } + else{ + raw = mn_m2raw(msgmap, n1); + if(raw > 0L + && (!(limitsrch && *limitsrch) + || in_searchset(*limitsrch, (unsigned long) raw))) + mm_searched(stream, raw); + } + + if(*p == '\0') + break; + } + + return(0); +} + + +/* + * Select by thread number ranges. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_thrd_number(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **msgset) +{ + int r, end; + long n1, n2; + char number1[16], number2[16], numbers[80], *p, *t; + HelpType help; + PINETHRD_S *thrd = NULL; + MESSAGECACHE *mc; + + numbers[0] = '\0'; + ps_global->mangled_footer = 1; + help = NO_HELP; + while(1){ + int flags = OE_APPEND_CURRENT; + + r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, + sizeof(numbers), _(select_num), NULL, help, &flags); + if(r == 4) + continue; + + if(r == 3){ + help = (help == NO_HELP) ? h_select_by_thrdnum : NO_HELP; + continue; + } + + for(t = p = numbers; *p ; p++) /* strip whitespace */ + if(!isspace((unsigned char)*p)) + *t++ = *p; + + *t = '\0'; + + if(r == 1 || numbers[0] == '\0'){ + cmd_cancelled("Selection by number"); + return(1); + } + else + break; + } + + for(n1 = 1; n1 <= stream->nmsgs; n1++) + if((mc = mail_elt(stream, n1)) != NULL) + mc->searched = 0; /* clear searched bits */ + + for(p = numbers; *p ; p++){ + t = number1; + while(*p && isdigit((unsigned char)*p)) + *t++ = *p++; + + *t = '\0'; + + end = 0; + if(number1[0] == '\0'){ + if(*p == '-'){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid number range, missing number before \"-\": %s"), + numbers); + return(1); + } + else if(!strucmp("end", p)){ + end = 1; + p += strlen("end"); + } + else{ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid thread number: %s"), numbers); + return(1); + } + } + + if(end) + n1 = msgmap->max_thrdno; + else if((n1 = atol(number1)) < 1L || n1 > msgmap->max_thrdno){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("\"%s\" out of thread number range"), + long2string(n1)); + return(1); + } + + t = number2; + if(*p == '-'){ + + while(*++p && isdigit((unsigned char)*p)) + *t++ = *p; + + *t = '\0'; + + end = 0; + if(number2[0] == '\0'){ + if(!strucmp("end", p)){ + end = 1; + p += strlen("end"); + } + else{ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid number range, missing number after \"-\": %s"), + numbers); + return(1); + } + } + + if(end) + n2 = msgmap->max_thrdno; + else if((n2 = atol(number2)) < 1L || n2 > msgmap->max_thrdno){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("\"%s\" out of thread number range"), + long2string(n2)); + return(1); + } + + if(n2 <= n1){ + char t[20]; + + strncpy(t, long2string(n1), sizeof(t)); + t[sizeof(t)-1] = '\0'; + q_status_message2(SM_ORDER | SM_DING, 0, 2, + _("Invalid reverse message number range: %s-%s"), + t, long2string(n2)); + return(1); + } + + for(;n1 <= n2; n1++){ + thrd = find_thread_by_number(stream, msgmap, n1, thrd); + + if(thrd) + set_search_bit_for_thread(stream, thrd, msgset); + } + } + else{ + thrd = find_thread_by_number(stream, msgmap, n1, NULL); + + if(thrd) + set_search_bit_for_thread(stream, thrd, msgset); + } + + if(*p == '\0') + break; + } + + return(0); +} + + +/* + * Select by message dates. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_date(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch) +{ + int r, we_cancel = 0, when = 0; + char date[100], defdate[100], prompt[128]; + time_t seldate = time(0); + struct tm *seldate_tm; + SEARCHPGM *pgm; + HelpType help; + static struct _tense { + char *preamble, + *range, + *scope; + } tense[] = { + {"were ", "SENT SINCE", " (inclusive)"}, + {"were ", "SENT BEFORE", " (exclusive)"}, + {"were ", "SENT ON", "" }, + {"", "ARRIVED SINCE", " (inclusive)"}, + {"", "ARRIVED BEFORE", " (exclusive)"}, + {"", "ARRIVED ON", "" } + }; + + date[0] = '\0'; + ps_global->mangled_footer = 1; + help = NO_HELP; + + /* + * If talking to an old server, default to SINCE instead of + * SENTSINCE, which was added later. + */ + if(is_imap_stream(stream) && !modern_imap_stream(stream)) + when = 3; + + while(1){ + int flags = OE_APPEND_CURRENT; + + seldate_tm = localtime(&seldate); + snprintf(defdate, sizeof(defdate), "%.2d-%.4s-%.4d", seldate_tm->tm_mday, + month_abbrev(seldate_tm->tm_mon + 1), + seldate_tm->tm_year + 1900); + defdate[sizeof(defdate)-1] = '\0'; + snprintf(prompt,sizeof(prompt),"Select messages which %s%s%s [%s]: ", + tense[when].preamble, tense[when].range, + tense[when].scope, defdate); + prompt[sizeof(prompt)-1] = '\0'; + r = optionally_enter(date,-FOOTER_ROWS(ps_global), 0, sizeof(date), + prompt, sel_date_opt, help, &flags); + switch (r){ + case 1 : + cmd_cancelled("Selection by date"); + return(1); + + case 3 : + help = (help == NO_HELP) ? h_select_date : NO_HELP; + continue; + + case 4 : + continue; + + case 11 : + { + MESSAGECACHE *mc; + long rawno; + + if(stream && (rawno = mn_m2raw(msgmap, msgno)) > 0L + && rawno <= stream->nmsgs + && (mc = mail_elt(stream, rawno))){ + + /* cache not filled in yet? */ + if(mc->day == 0){ + char seq[20]; + + if(stream->dtb && stream->dtb->flags & DR_NEWS){ + strncpy(seq, + ulong2string(mail_uid(stream, rawno)), + sizeof(seq)); + seq[sizeof(seq)-1] = '\0'; + mail_fetch_overview(stream, seq, NULL); + } + else{ + strncpy(seq, long2string(rawno), + sizeof(seq)); + seq[sizeof(seq)-1] = '\0'; + mail_fetch_fast(stream, seq, 0L); + } + } + + /* mail_date returns fixed field width date */ + mail_date(date, mc); + date[11] = '\0'; + } + } + + continue; + + case 12 : /* set default to PREVIOUS day */ + seldate -= 86400; + continue; + + case 13 : /* set default to NEXT day */ + seldate += 86400; + continue; + + case 14 : + when = (when+1) % (sizeof(tense) / sizeof(struct _tense)); + continue; + + default: + break; + } + + removing_leading_white_space(date); + removing_trailing_white_space(date); + if(!*date){ + strncpy(date, defdate, sizeof(date)); + date[sizeof(date)-1] = '\0'; + } + + break; + } + + if((pgm = mail_newsearchpgm()) != NULL){ + MESSAGECACHE elt; + short converted_date; + + if(mail_parse_date(&elt, (unsigned char *) date)){ + converted_date = mail_shortdate(elt.year, elt.month, elt.day); + + switch(when){ + case 0: + pgm->sentsince = converted_date; + break; + case 1: + pgm->sentbefore = converted_date; + break; + case 2: + pgm->senton = converted_date; + break; + case 3: + pgm->since = converted_date; + break; + case 4: + pgm->before = converted_date; + break; + case 5: + pgm->on = converted_date; + break; + } + + pgm->msgno = (limitsrch ? *limitsrch : NULL); + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + + pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); + + if(we_cancel) + cancel_busy_cue(0); + + /* we know this was freed in mail_search, let caller know */ + if(limitsrch) + *limitsrch = NULL; + } + else{ + mail_free_searchpgm(&pgm); + q_status_message1(SM_ORDER, 3, 3, + _("Invalid date entered: %s"), date); + return(1); + } + } + + return(0); +} + + +/* + * Select by searching in message headers or body. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_text(MAILSTREAM *stream, MSGNO_S *msgmap, long int msgno, SEARCHSET **limitsrch) +{ + int r, ku, type, we_cancel = 0, flags, rv, ekeyi = 0; + int not = 0, me = 0; + char sstring[80], savedsstring[80], tmp[128]; + char *p, *sval = NULL; + char buftmp[MAILTMPLEN], namehdr[80]; + ESCKEY_S ekey[8]; + ENVELOPE *env = NULL; + HelpType help; + unsigned flagsforhist = 0; + static HISTORY_S *history = NULL; + static char *recip = "RECIPIENTS"; + static char *partic = "PARTICIPANTS"; + static char *match_me = N_("[Match_My_Addresses]"); + static char *dont_match_me = N_("[Don't_Match_My_Addresses]"); + + ps_global->mangled_footer = 1; + savedsstring[0] = '\0'; + ekey[0].ch = ekey[1].ch = ekey[2].ch = ekey[3].ch = -1; + + while(1){ + type = radio_buttons(not ? _(sel_text_not) : _(sel_text), + -FOOTER_ROWS(ps_global), sel_text_opt, + 's', 'x', NO_HELP, RB_NORM|RB_RET_HELP); + + if(type == '!') + not = !not; + else if(type == 3){ + helper(h_select_text, "HELP FOR SELECT BASED ON CONTENTS", + HLPD_SIMPLE); + ps_global->mangled_screen = 1; + } + else + break; + } + + /* + * prepare some friendly defaults... + */ + switch(type){ + case 't' : /* address fields, offer To or From */ + case 'f' : + case 'c' : + case 'r' : + case 'p' : + sval = (type == 't') ? "TO" : + (type == 'f') ? "FROM" : + (type == 'c') ? "CC" : + (type == 'r') ? recip : partic; + ekey[ekeyi].ch = ctrl('T'); + ekey[ekeyi].name = "^T"; + ekey[ekeyi].rval = 10; + /* TRANSLATORS: use Current To Address */ + ekey[ekeyi++].label = N_("Cur To"); + ekey[ekeyi].ch = ctrl('R'); + ekey[ekeyi].name = "^R"; + ekey[ekeyi].rval = 11; + /* TRANSLATORS: use Current From Address */ + ekey[ekeyi++].label = N_("Cur From"); + ekey[ekeyi].ch = ctrl('W'); + ekey[ekeyi].name = "^W"; + ekey[ekeyi].rval = 12; + /* TRANSLATORS: use Current Cc Address */ + ekey[ekeyi++].label = N_("Cur Cc"); + ekey[ekeyi].ch = ctrl('Y'); + ekey[ekeyi].name = "^Y"; + ekey[ekeyi].rval = 13; + /* TRANSLATORS: Match Me means match my address */ + ekey[ekeyi++].label = N_("Match Me"); + ekey[ekeyi].ch = 0; + ekey[ekeyi].name = ""; + ekey[ekeyi].rval = 0; + ekey[ekeyi++].label = ""; + break; + + case 's' : + sval = "SUBJECT"; + ekey[ekeyi].ch = ctrl('X'); + ekey[ekeyi].name = "^X"; + ekey[ekeyi].rval = 14; + /* TRANSLATORS: use Current Subject */ + ekey[ekeyi++].label = N_("Cur Subject"); + break; + + case 'a' : + sval = "TEXT"; + break; + + case 'b' : + sval = "BODYTEXT"; + break; + + case 'h' : + strcpy(tmp, "Name of HEADER to match : "); + flags = OE_APPEND_CURRENT; + namehdr[0] = '\0'; + r = 'x'; + while (r == 'x'){ + int done = 0; + + r = optionally_enter(namehdr, -FOOTER_ROWS(ps_global), 0, + sizeof(namehdr), tmp, ekey, NO_HELP, &flags); + if (r == 1){ + cmd_cancelled("Selection by text"); + return(1); + } + removing_leading_white_space(namehdr); + while(!done){ + while ((namehdr[0] != '\0') && /* remove trailing ":" */ + (namehdr[strlen(namehdr) - 1] == ':')) + namehdr[strlen(namehdr) - 1] = '\0'; + if ((namehdr[0] != '\0') + && isspace((unsigned char) namehdr[strlen(namehdr) - 1])) + removing_trailing_white_space(namehdr); + else + done++; + } + if (strchr(namehdr,' ') || strchr(namehdr,'\t') || + strchr(namehdr,':')) + namehdr[0] = '\0'; + if (namehdr[0] == '\0') + r = 'x'; + } + sval = namehdr; + break; + + case 'x': + break; + + default: + dprint((1,"\n - BOTCH: select_text unrecognized option\n")); + return(1); + } + + ekey[ekeyi].ch = KEY_UP; + ekey[ekeyi].rval = 30; + ekey[ekeyi].name = ""; + ku = ekeyi; + ekey[ekeyi++].label = ""; + + ekey[ekeyi].ch = KEY_DOWN; + ekey[ekeyi].rval = 31; + ekey[ekeyi].name = ""; + ekey[ekeyi++].label = ""; + + ekey[ekeyi].ch = -1; + + if(type != 'x'){ + + init_hist(&history, HISTSIZE); + + if(ekey[0].ch > -1 && msgno > 0L + && !(env=pine_mail_fetchstructure(stream,mn_m2raw(msgmap,msgno), + NULL))) + ekey[0].ch = -1; + + sstring[0] = '\0'; + help = NO_HELP; + r = type; + while(r != 'x'){ + if(not) + /* TRANSLATORS: character String in message <message number> to NOT match : " */ + snprintf(tmp, sizeof(tmp), "String in message %s to NOT match : ", sval); + else + snprintf(tmp, sizeof(tmp), "String in message %s to match : ", sval); + + if(items_in_hist(history) > 0){ + ekey[ku].name = HISTORY_UP_KEYNAME; + ekey[ku].label = HISTORY_KEYLABEL; + ekey[ku+1].name = HISTORY_DOWN_KEYNAME; + ekey[ku+1].label = HISTORY_KEYLABEL; + } + else{ + ekey[ku].name = ""; + ekey[ku].label = ""; + ekey[ku+1].name = ""; + ekey[ku+1].label = ""; + } + + flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE; + r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0, + 79, tmp, ekey, help, &flags); + + if(me && r == 0 && ((!not && strcmp(sstring, _(match_me))) || (not && strcmp(sstring, _(dont_match_me))))) + me = 0; + + switch(r){ + case 3 : + help = (help == NO_HELP) + ? (not + ? ((type == 'f') ? h_select_txt_not_from + : (type == 't') ? h_select_txt_not_to + : (type == 'c') ? h_select_txt_not_cc + : (type == 's') ? h_select_txt_not_subj + : (type == 'a') ? h_select_txt_not_all + : (type == 'r') ? h_select_txt_not_recip + : (type == 'p') ? h_select_txt_not_partic + : (type == 'b') ? h_select_txt_not_body + : NO_HELP) + : ((type == 'f') ? h_select_txt_from + : (type == 't') ? h_select_txt_to + : (type == 'c') ? h_select_txt_cc + : (type == 's') ? h_select_txt_subj + : (type == 'a') ? h_select_txt_all + : (type == 'r') ? h_select_txt_recip + : (type == 'p') ? h_select_txt_partic + : (type == 'b') ? h_select_txt_body + : NO_HELP)) + : NO_HELP; + + case 4 : + continue; + + case 10 : /* To: default */ + if(env && env->to && env->to->mailbox){ + snprintf(sstring, sizeof(sstring), "%s%s%s", env->to->mailbox, + env->to->host ? "@" : "", + env->to->host ? env->to->host : ""); + sstring[sizeof(sstring)-1] = '\0'; + } + continue; + + case 11 : /* From: default */ + if(env && env->from && env->from->mailbox){ + snprintf(sstring, sizeof(sstring), "%s%s%s", env->from->mailbox, + env->from->host ? "@" : "", + env->from->host ? env->from->host : ""); + sstring[sizeof(sstring)-1] = '\0'; + } + continue; + + case 12 : /* Cc: default */ + if(env && env->cc && env->cc->mailbox){ + snprintf(sstring, sizeof(sstring), "%s%s%s", env->cc->mailbox, + env->cc->host ? "@" : "", + env->cc->host ? env->cc->host : ""); + sstring[sizeof(sstring)-1] = '\0'; + } + continue; + + case 13 : /* Match my addresses */ + me++; + snprintf(sstring, sizeof(sstring), not ? _(dont_match_me) : _(match_me)); + continue; + + case 14 : /* Subject: default */ + if(env && env->subject && env->subject[0]){ + char *q = NULL; + + snprintf(buftmp, sizeof(buftmp), "%.75s", env->subject); + buftmp[sizeof(buftmp)-1] = '\0'; + q = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, + SIZEOF_20KBUF, buftmp); + if(q != env->subject){ + snprintf(savedsstring, sizeof(savedsstring), "%.70s", q); + savedsstring[sizeof(savedsstring)-1] = '\0'; + } + + snprintf(sstring, sizeof(sstring), "%s", q); + sstring[sizeof(sstring)-1] = '\0'; + } + + continue; + + case 30 : + flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0); + if((p = get_prev_hist(history, sstring, flagsforhist, NULL)) != NULL){ + strncpy(sstring, p, sizeof(sstring)); + sstring[sizeof(sstring)-1] = '\0'; + if(history->hist[history->curindex]){ + flagsforhist = history->hist[history->curindex]->flags; + not = (flagsforhist & 0x1) ? 1 : 0; + me = (flagsforhist & 0x2) ? 1 : 0; + } + } + else + Writechar(BELL, 0); + + continue; + + case 31 : + flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0); + if((p = get_next_hist(history, sstring, flagsforhist, NULL)) != NULL){ + strncpy(sstring, p, sizeof(sstring)); + sstring[sizeof(sstring)-1] = '\0'; + if(history->hist[history->curindex]){ + flagsforhist = history->hist[history->curindex]->flags; + not = (flagsforhist & 0x1) ? 1 : 0; + me = (flagsforhist & 0x2) ? 1 : 0; + } + } + else + Writechar(BELL, 0); + + continue; + + default : + break; + } + + if(r == 1 || sstring[0] == '\0') + r = 'x'; + + break; + } + } + + if(type == 'x' || r == 'x'){ + cmd_cancelled("Selection by text"); + return(1); + } + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + + flagsforhist = (not ? 0x1 : 0) + (me ? 0x2 : 0); + save_hist(history, sstring, flagsforhist, NULL); + + rv = agg_text_select(stream, msgmap, type, namehdr, not, me, sstring, "utf-8", limitsrch); + if(we_cancel) + cancel_busy_cue(0); + + return(rv); +} + + +/* + * Select by message size. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_size(MAILSTREAM *stream, SEARCHSET **limitsrch) +{ + int r, large = 1, we_cancel = 0; + unsigned long n, mult = 1L, numerator = 0L, divisor = 1L; + char size[16], numbers[80], *p, *t; + HelpType help; + SEARCHPGM *pgm; + long flags = (SE_NOPREFETCH | SE_FREE); + + numbers[0] = '\0'; + ps_global->mangled_footer = 1; + + help = NO_HELP; + while(1){ + int flgs = OE_APPEND_CURRENT; + + sel_size_opt[1].label = large ? sel_size_smaller : sel_size_larger; + + r = optionally_enter(numbers, -FOOTER_ROWS(ps_global), 0, + sizeof(numbers), large ? _(select_size_larger_msg) + : _(select_size_smaller_msg), + sel_size_opt, help, &flgs); + if(r == 4) + continue; + + if(r == 14){ + large = 1 - large; + continue; + } + + if(r == 3){ + help = (help == NO_HELP) ? (large ? h_select_by_larger_size + : h_select_by_smaller_size) + : NO_HELP; + continue; + } + + for(t = p = numbers; *p ; p++) /* strip whitespace */ + if(!isspace((unsigned char)*p)) + *t++ = *p; + + *t = '\0'; + + if(r == 1 || numbers[0] == '\0'){ + cmd_cancelled("Selection by size"); + return(1); + } + else + break; + } + + if(numbers[0] == '-'){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid size entered: %s"), numbers); + return(1); + } + + t = size; + p = numbers; + + while(*p && isdigit((unsigned char)*p)) + *t++ = *p++; + + *t = '\0'; + + if(size[0] == '\0' && *p == '.' && isdigit(*(p+1))){ + size[0] = '0'; + size[1] = '\0'; + } + + if(size[0] == '\0'){ + q_status_message1(SM_ORDER | SM_DING, 0, 2, + _("Invalid size entered: %s"), numbers); + return(1); + } + + n = strtoul(size, (char **)NULL, 10); + + size[0] = '\0'; + if(*p == '.'){ + /* + * We probably ought to just use atof() to convert 1.1 into a + * double, but since we haven't used atof() anywhere else I'm + * reluctant to use it because of portability concerns. + */ + p++; + t = size; + while(*p && isdigit((unsigned char)*p)){ + *t++ = *p++; + divisor *= 10; + } + + *t = '\0'; + + if(size[0]) + numerator = strtoul(size, (char **)NULL, 10); + } + + switch(*p){ + case 'g': + case 'G': + mult *= 1000; + /* fall through */ + + case 'm': + case 'M': + mult *= 1000; + /* fall through */ + + case 'k': + case 'K': + mult *= 1000; + break; + } + + n = n * mult + (numerator * mult) / divisor; + + pgm = mail_newsearchpgm(); + if(large) + pgm->larger = n; + else + pgm->smaller = n; + + if(is_imap_stream(stream) && !modern_imap_stream(stream)) + flags |= SE_NOSERVER; + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + + pgm->msgno = (limitsrch ? *limitsrch : NULL); + pine_mail_search_full(stream, NULL, pgm, SE_NOPREFETCH | SE_FREE); + /* we know this was freed in mail_search, let caller know */ + if(limitsrch) + *limitsrch = NULL; + + if(we_cancel) + cancel_busy_cue(0); + + return(0); +} + + +/* + * visible_searchset -- return c-client search set unEXLDed + * sequence numbers + */ +SEARCHSET * +visible_searchset(MAILSTREAM *stream, MSGNO_S *msgmap) +{ + long n, run; + SEARCHSET *full_set = NULL, **set; + + /* + * If we're talking to anything other than a server older than + * imap 4rev1, build a searchset otherwise it'll choke. + */ + if(!(is_imap_stream(stream) && !modern_imap_stream(stream))){ + if(any_lflagged(msgmap, MN_EXLD)){ + for(n = 1L, set = &full_set, run = 0L; n <= stream->nmsgs; n++) + if(get_lflag(stream, NULL, n, MN_EXLD)){ + if(run){ /* previous NOT excluded? */ + if(run > 1L) + (*set)->last = n - 1L; + + set = &(*set)->next; + run = 0L; + } + } + else if(run++){ /* next in run */ + (*set)->last = n; + } + else{ /* start of run */ + *set = mail_newsearchset(); + (*set)->first = n; + } + } + else{ + full_set = mail_newsearchset(); + full_set->first = 1L; + full_set->last = stream->nmsgs; + } + } + + return(full_set); +} + + +/* + * Select by message status bits. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_status(MAILSTREAM *stream, SEARCHSET **limitsrch) +{ + int s, not = 0, we_cancel = 0, rv; + + while(1){ + s = radio_buttons((not) ? _(sel_flag_not) : _(sel_flag), + -FOOTER_ROWS(ps_global), sel_flag_opt, '*', 'x', + NO_HELP, RB_NORM|RB_RET_HELP); + + if(s == 'x'){ + cmd_cancelled("Selection by status"); + return(1); + } + else if(s == 3){ + helper(h_select_status, _("HELP FOR SELECT BASED ON STATUS"), + HLPD_SIMPLE); + ps_global->mangled_screen = 1; + } + else if(s == '!') + not = !not; + else + break; + } + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + rv = agg_flag_select(stream, not, s, limitsrch); + if(we_cancel) + cancel_busy_cue(0); + + return(rv); +} + + +/* + * Select by rule. Usually srch, indexcolor, and roles would be most useful. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_rule(MAILSTREAM *stream, SEARCHSET **limitsrch) +{ + char rulenick[1000], *nick; + PATGRP_S *patgrp; + int r, not = 0, we_cancel = 0, rflags = ROLE_DO_SRCH + | ROLE_DO_INCOLS + | ROLE_DO_ROLES + | ROLE_DO_SCORES + | ROLE_DO_OTHER + | ROLE_DO_FILTER; + + rulenick[0] = '\0'; + ps_global->mangled_footer = 1; + + do{ + int oe_flags; + + oe_flags = OE_APPEND_CURRENT; + r = optionally_enter(rulenick, -FOOTER_ROWS(ps_global), 0, + sizeof(rulenick), + not ? _("Rule to NOT match: ") + : _("Rule to match: "), + sel_key_opt, NO_HELP, &oe_flags); + + if(r == 14){ + /* select rulenick from a list */ + if((nick=choose_a_rule(rflags)) != NULL){ + strncpy(rulenick, nick, sizeof(rulenick)-1); + rulenick[sizeof(rulenick)-1] = '\0'; + fs_give((void **) &nick); + } + else + r = 4; + } + else if(r == '!') + not = !not; + + if(r == 3){ + helper(h_select_rule, _("HELP FOR SELECT BY RULE"), HLPD_SIMPLE); + ps_global->mangled_screen = 1; + } + else if(r == 1){ + cmd_cancelled("Selection by Rule"); + return(1); + } + + removing_leading_and_trailing_white_space(rulenick); + + }while(r == 3 || r == 4 || r == '!'); + + + /* + * The approach of requiring a nickname instead of just allowing the + * user to select from the list of rules has the drawback that a rule + * may not have a nickname, or there may be more than one rule with + * the same nickname. However, it has the benefit of allowing the user + * to type in the nickname and, most importantly, allows us to set + * up the ! (not). We could incorporate the ! into the selection + * screen, but this is easier and also allows the typing of nicks. + * User can just set up nicknames if they want to use this feature. + */ + patgrp = nick_to_patgrp(rulenick, rflags); + + if(patgrp){ + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + match_pattern(patgrp, stream, limitsrch ? *limitsrch : 0, NULL, + get_msg_score, + (not ? MP_NOT : 0) | SE_NOPREFETCH); + free_patgrp(&patgrp); + if(we_cancel) + cancel_busy_cue(0); + } + + if(limitsrch && *limitsrch){ + mail_free_searchset(limitsrch); + *limitsrch = NULL; + } + + return(0); +} + + +/* + * Allow user to choose a rule from their list of rules. + * + * Returns an allocated rule nickname on success, NULL otherwise. + */ +char * +choose_a_rule(int rflags) +{ + char *choice = NULL; + char **rule_list, **lp; + int cnt = 0; + PAT_S *pat; + PAT_STATE pstate; + + if(!(nonempty_patterns(rflags, &pstate) && first_pattern(&pstate))){ + q_status_message(SM_ORDER, 3, 3, + _("No rules available. Use Setup/Rules to add some.")); + return(choice); + } + + /* + * Build a list of rules to choose from. + */ + + for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) + cnt++; + + if(cnt <= 0){ + q_status_message(SM_ORDER, 3, 4, _("No rules defined, use Setup/Rules")); + return(choice); + } + + lp = rule_list = (char **) fs_get((cnt + 1) * sizeof(*rule_list)); + memset(rule_list, 0, (cnt+1) * sizeof(*rule_list)); + + for(pat = first_pattern(&pstate); pat; pat = next_pattern(&pstate)) + *lp++ = cpystr((pat->patgrp && pat->patgrp->nick) + ? pat->patgrp->nick : "?"); + + /* TRANSLATORS: SELECT A RULE is a screen title + TRANSLATORS: Print something1 using something2. + "rules" is something1 */ + choice = choose_item_from_list(rule_list, NULL, _("SELECT A RULE"), + _("rules"), h_select_rule_screen, + _("HELP FOR SELECTING A RULE NICKNAME"), NULL); + + if(!choice) + q_status_message(SM_ORDER, 1, 4, "No choice"); + + free_list_array(&rule_list); + + return(choice); +} + + +/* + * Select by current thread. + * Sets searched bits in mail_elts for this entire thread + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_thread(MAILSTREAM *stream, MSGNO_S *msgmap, SEARCHSET **limitsrch) +{ + long n; + PINETHRD_S *thrd = NULL; + int ret = 1; + MESSAGECACHE *mc; + + if(!stream) + return(ret); + + for(n = 1L; n <= stream->nmsgs; n++) + if((mc = mail_elt(stream, n)) != NULL) + mc->searched = 0; /* clear searched bits */ + + thrd = fetch_thread(stream, mn_m2raw(msgmap, mn_get_cur(msgmap))); + if(thrd && thrd->top && thrd->top != thrd->rawno) + thrd = fetch_thread(stream, thrd->top); + + /* + * This doesn't unselect if the thread is already selected + * (like select current does), it always selects. + * There is no way to select ! this thread. + */ + if(thrd){ + set_search_bit_for_thread(stream, thrd, limitsrch); + ret = 0; + } + + return(ret); +} + + +/* + * Select by message keywords. + * Sets searched bits in mail_elts + * + * Args limitsrch -- limit search to this searchset + * + * Returns 0 on success. + */ +int +select_by_keyword(MAILSTREAM *stream, SEARCHSET **limitsrch) +{ + int r, not = 0, we_cancel = 0; + char keyword[MAXUSERFLAG+1], *kword; + char *error = NULL, *p, *prompt; + HelpType help; + SEARCHPGM *pgm; + + keyword[0] = '\0'; + ps_global->mangled_footer = 1; + + help = NO_HELP; + do{ + int oe_flags; + + if(error){ + q_status_message(SM_ORDER, 3, 4, error); + fs_give((void **) &error); + } + + if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){ + if(not) + prompt = _("Keyword (or keyword initial) to NOT match: "); + else + prompt = _("Keyword (or keyword initial) to match: "); + } + else{ + if(not) + prompt = _("Keyword to NOT match: "); + else + prompt = _("Keyword to match: "); + } + + oe_flags = OE_APPEND_CURRENT; + r = optionally_enter(keyword, -FOOTER_ROWS(ps_global), 0, + sizeof(keyword), + prompt, sel_key_opt, help, &oe_flags); + + if(r == 14){ + /* select keyword from a list */ + if((kword=choose_a_keyword()) != NULL){ + strncpy(keyword, kword, sizeof(keyword)-1); + keyword[sizeof(keyword)-1] = '\0'; + fs_give((void **) &kword); + } + else + r = 4; + } + else if(r == '!') + not = !not; + + if(r == 3) + help = help == NO_HELP ? h_select_keyword : NO_HELP; + else if(r == 1){ + cmd_cancelled("Selection by keyword"); + return(1); + } + + removing_leading_and_trailing_white_space(keyword); + + }while(r == 3 || r == 4 || r == '!' || keyword_check(keyword, &error)); + + + if(F_ON(F_FLAG_SCREEN_KW_SHORTCUT, ps_global) && ps_global->keywords){ + p = initial_to_keyword(keyword); + if(p != keyword){ + strncpy(keyword, p, sizeof(keyword)-1); + keyword[sizeof(keyword)-1] = '\0'; + } + } + + /* + * We want to check the keyword, not the nickname of the keyword, + * so convert it to the keyword if necessary. + */ + p = nick_to_keyword(keyword); + if(p != keyword){ + strncpy(keyword, p, sizeof(keyword)-1); + keyword[sizeof(keyword)-1] = '\0'; + } + + pgm = mail_newsearchpgm(); + if(not){ + pgm->unkeyword = mail_newstringlist(); + pgm->unkeyword->text.data = (unsigned char *) cpystr(keyword); + pgm->unkeyword->text.size = strlen(keyword); + } + else{ + pgm->keyword = mail_newstringlist(); + pgm->keyword->text.data = (unsigned char *) cpystr(keyword); + pgm->keyword->text.size = strlen(keyword); + } + + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + we_cancel = busy_cue(_("Selecting"), NULL, 1); + + pgm->msgno = (limitsrch ? *limitsrch : NULL); + pine_mail_search_full(stream, "UTF-8", pgm, SE_NOPREFETCH | SE_FREE); + /* we know this was freed in mail_search, let caller know */ + if(limitsrch) + *limitsrch = NULL; + + if(we_cancel) + cancel_busy_cue(0); + + return(0); +} + + +/* + * Allow user to choose a keyword from their list of keywords. + * + * Returns an allocated keyword on success, NULL otherwise. + */ +char * +choose_a_keyword(void) +{ + char *choice = NULL; + char **keyword_list, **lp; + int cnt; + KEYWORD_S *kw; + + /* + * Build a list of keywords to choose from. + */ + + for(cnt = 0, kw = ps_global->keywords; kw; kw = kw->next) + cnt++; + + if(cnt <= 0){ + q_status_message(SM_ORDER, 3, 4, + _("No keywords defined, use \"keywords\" option in Setup/Config")); + return(choice); + } + + lp = keyword_list = (char **) fs_get((cnt + 1) * sizeof(*keyword_list)); + memset(keyword_list, 0, (cnt+1) * sizeof(*keyword_list)); + + for(kw = ps_global->keywords; kw; kw = kw->next) + *lp++ = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : ""); + + /* TRANSLATORS: SELECT A KEYWORD is a screen title + TRANSLATORS: Print something1 using something2. + "keywords" is something1 */ + choice = choose_item_from_list(keyword_list, NULL, _("SELECT A KEYWORD"), + _("keywords"), h_select_keyword_screen, + _("HELP FOR SELECTING A KEYWORD"), NULL); + + if(!choice) + q_status_message(SM_ORDER, 1, 4, "No choice"); + + free_list_array(&keyword_list); + + return(choice); +} + + +/* + * Allow user to choose a list of keywords from their list of keywords. + * + * Returns allocated list. + */ +char ** +choose_list_of_keywords(void) +{ + LIST_SEL_S *listhead, *ls, *p; + char **ret = NULL; + int cnt, i; + KEYWORD_S *kw; + + /* + * Build a list of keywords to choose from. + */ + + p = listhead = NULL; + for(kw = ps_global->keywords; kw; kw = kw->next){ + + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + ls->item = cpystr(kw->nick ? kw->nick : kw->kw ? kw->kw : ""); + + if(p){ + p->next = ls; + p = p->next; + } + else + listhead = p = ls; + } + + if(!listhead) + return(ret); + + /* TRANSLATORS: SELECT KEYWORDS is a screen title + Print something1 using something2. + "keywords" is something1 */ + if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE, + _("SELECT KEYWORDS"), _("keywords"), + h_select_multkeyword_screen, + _("HELP FOR SELECTING KEYWORDS"), NULL)){ + for(cnt = 0, p = listhead; p; p = p->next) + if(p->selected) + cnt++; + + ret = (char **) fs_get((cnt+1) * sizeof(*ret)); + memset(ret, 0, (cnt+1) * sizeof(*ret)); + for(i = 0, p = listhead; p; p = p->next) + if(p->selected) + ret[i++] = cpystr(p->item ? p->item : ""); + } + + free_list_sel(&listhead); + + return(ret); +} + + +/* + * Allow user to choose a charset + * + * Returns an allocated charset on success, NULL otherwise. + */ +char * +choose_a_charset(int which_charsets) +{ + char *choice = NULL; + char **charset_list, **lp; + const CHARSET *cs; + int cnt; + + /* + * Build a list of charsets to choose from. + */ + + for(cnt = 0, cs = utf8_charset(NIL); cs && cs->name; cs++){ + if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL)) + && ((which_charsets & CAC_ALL) + || (which_charsets & CAC_POSTING + && cs->flags & CF_POSTING) + || (which_charsets & CAC_DISPLAY + && cs->type != CT_2022 + && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY)))) + cnt++; + } + + if(cnt <= 0){ + q_status_message(SM_ORDER, 3, 4, + _("No charsets found? Enter charset manually.")); + return(choice); + } + + lp = charset_list = (char **) fs_get((cnt + 1) * sizeof(*charset_list)); + memset(charset_list, 0, (cnt+1) * sizeof(*charset_list)); + + for(cs = utf8_charset(NIL); cs && cs->name; cs++){ + if(!(cs->flags & (CF_UNSUPRT|CF_NOEMAIL)) + && ((which_charsets & CAC_ALL) + || (which_charsets & CAC_POSTING + && cs->flags & CF_POSTING) + || (which_charsets & CAC_DISPLAY + && cs->type != CT_2022 + && (cs->flags & (CF_PRIMARY|CF_DISPLAY)) == (CF_PRIMARY|CF_DISPLAY)))) + *lp++ = cpystr(cs->name); + } + + /* TRANSLATORS: SELECT A CHARACTER SET is a screen title + TRANSLATORS: Print something1 using something2. + "character sets" is something1 */ + choice = choose_item_from_list(charset_list, NULL, _("SELECT A CHARACTER SET"), + _("character sets"), h_select_charset_screen, + _("HELP FOR SELECTING A CHARACTER SET"), NULL); + + if(!choice) + q_status_message(SM_ORDER, 1, 4, "No choice"); + + free_list_array(&charset_list); + + return(choice); +} + + +/* + * Allow user to choose a list of character sets and/or scripts + * + * Returns allocated list. + */ +char ** +choose_list_of_charsets(void) +{ + LIST_SEL_S *listhead, *ls, *p; + char **ret = NULL; + int cnt, i, got_one; + const CHARSET *cs; + SCRIPT *s; + char *q, *t; + long width, limit; + char buf[1024], *folded; + + /* + * Build a list of charsets to choose from. + */ + + p = listhead = NULL; + + /* this width is determined by select_from_list_screen() */ + width = ps_global->ttyo->screen_cols - 4; + + /* first comes a list of scripts (sets of character sets) */ + for(s = utf8_script(NIL); s && s->name; s++){ + + limit = sizeof(buf)-1; + q = buf; + memset(q, 0, limit+1); + + if(s->name) + sstrncpy(&q, s->name, limit); + + if(s->description){ + sstrncpy(&q, " (", limit-(q-buf)); + sstrncpy(&q, s->description, limit-(q-buf)); + sstrncpy(&q, ")", limit-(q-buf)); + } + + /* add the list of charsets that are in this script */ + got_one = 0; + for(cs = utf8_charset(NIL); + cs && cs->name && (q-buf) < limit; cs++){ + if(cs->script & s->script){ + /* + * Filter out some un-useful members of the list. + * UTF-7 and UTF-8 weren't actually in the list at the + * time this was written. Just making sure. + */ + if(!strucmp(cs->name, "ISO-2022-JP-2") + || !strucmp(cs->name, "UTF-7") + || !strucmp(cs->name, "UTF-8")) + continue; + + if(got_one) + sstrncpy(&q, " ", limit-(q-buf)); + else{ + got_one = 1; + sstrncpy(&q, " {", limit-(q-buf)); + } + + sstrncpy(&q, cs->name, limit-(q-buf)); + } + } + + if(got_one) + sstrncpy(&q, "}", limit-(q-buf)); + + /* fold this line so that it can all be seen on the screen */ + folded = fold(buf, width, width, "", " ", FLD_NONE); + if(folded){ + t = folded; + while(t && *t && (q = strindex(t, '\n')) != NULL){ + *q = '\0'; + + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + if(t == folded) + ls->item = cpystr(s->name); + else + ls->flags = SFL_NOSELECT; + + ls->display_item = cpystr(t); + + t = q+1; + + if(p){ + p->next = ls; + p = p->next; + } + else{ + /* add a heading */ + listhead = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(listhead, 0, sizeof(*listhead)); + listhead->flags = SFL_NOSELECT; + listhead->display_item = + cpystr(_("Scripts representing groups of related character sets")); + listhead->next = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(listhead->next, 0, sizeof(*listhead)); + listhead->next->flags = SFL_NOSELECT; + listhead->next->display_item = + cpystr(repeat_char(width, '-')); + + listhead->next->next = ls; + p = ls; + } + } + + fs_give((void **) &folded); + } + } + + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + ls->flags = SFL_NOSELECT; + if(p){ + p->next = ls; + p = p->next; + } + else + listhead = p = ls; + + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + ls->flags = SFL_NOSELECT; + ls->display_item = + cpystr(_("Individual character sets, may be mixed with scripts")); + p->next = ls; + p = p->next; + + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + ls->flags = SFL_NOSELECT; + ls->display_item = + cpystr(repeat_char(width, '-')); + p->next = ls; + p = p->next; + + /* then comes a list of individual character sets */ + for(cs = utf8_charset(NIL); cs && cs->name; cs++){ + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + ls->item = cpystr(cs->name); + + if(p){ + p->next = ls; + p = p->next; + } + else + listhead = p = ls; + } + + if(!listhead) + return(ret); + + /* TRANSLATORS: SELECT CHARACTER SETS is a screen title + Print something1 using something2. + "character sets" is something1 */ + if(!select_from_list_screen(listhead, SFL_ALLOW_LISTMODE, + _("SELECT CHARACTER SETS"), _("character sets"), + h_select_multcharsets_screen, + _("HELP FOR SELECTING CHARACTER SETS"), NULL)){ + for(cnt = 0, p = listhead; p; p = p->next) + if(p->selected) + cnt++; + + ret = (char **) fs_get((cnt+1) * sizeof(*ret)); + memset(ret, 0, (cnt+1) * sizeof(*ret)); + for(i = 0, p = listhead; p; p = p->next) + if(p->selected) + ret[i++] = cpystr(p->item ? p->item : ""); + } + + free_list_sel(&listhead); + + return(ret); +} + +/* Report quota summary resources in an IMAP server */ + +void cmd_quota (struct pine *state) +{ + QUOTALIST *imapquota; + NETMBX mb; + STORE_S *store; + SCROLL_S sargs; + + if(!state->mail_stream || !is_imap_stream(state->mail_stream)){ + q_status_message(SM_ORDER, 1, 5, "Quota only available for IMAP folders"); + return; + } + + if (state->mail_stream + && !sp_dead_stream(state->mail_stream) + && state->mail_stream->mailbox + && *state->mail_stream->mailbox + && mail_valid_net_parse(state->mail_stream->mailbox, &mb)) + imap_getquotaroot(state->mail_stream, mb.mailbox); + + if(!state->quota) /* failed ? */ + return; /* go back... */ + + if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){ + q_status_message(SM_ORDER | SM_DING, 3, 3, "Error allocating space."); + return; + } + + so_puts(store, "Quota Report for "); + so_puts(store, state->mail_stream->original_mailbox); + so_puts(store, "\n\n"); + + for (imapquota = state->quota; imapquota; imapquota = imapquota->next){ + + so_puts(store, _("Resource : ")); + so_puts(store, imapquota->name); + so_writec('\n', store); + + so_puts(store, _("Usage : ")); + so_puts(store, long2string(imapquota->usage)); + if(!strucmp(imapquota->name,"STORAGE")) + so_puts(store, " KiB "); + if(!strucmp(imapquota->name,"MESSAGE")){ + so_puts(store, _(" message")); + if(imapquota->usage != 1) + so_puts(store, _("s ")); /* plural */ + else + so_puts(store, _(" ")); + } + so_writec('(', store); + so_puts(store, long2string(100*imapquota->usage/imapquota->limit)); + so_puts(store, "%)\n"); + + so_puts(store, _("Limit : ")); + so_puts(store, long2string(imapquota->limit)); + if(!strucmp(imapquota->name,"STORAGE")) + so_puts(store, " KiB\n\n"); + if(!strucmp(imapquota->name,"MESSAGE")){ + so_puts(store, _(" message")); + if(imapquota->usage != 1) + so_puts(store, _("s\n\n")); /* plural */ + else + so_puts(store, _("\n\n")); + } + } + + memset(&sargs, 0, sizeof(SCROLL_S)); + sargs.text.text = so_text(store); + sargs.text.src = CharStar; + sargs.text.desc = _("Quota Resources Summary"); + sargs.bar.title = _("QUOTA SUMMARY"); + sargs.proc.tool = NULL; + sargs.help.text = h_quota_command; + sargs.help.title = NULL; + sargs.keys.menu = &pine_quota_keymenu; + setbitmap(sargs.keys.bitmap); + + scrolltool(&sargs); + so_give(&store); + + if (state->quota) + mail_free_quotalist(&(state->quota)); +} + +/*---------------------------------------------------------------------- + Prompt the user for the type of sort he desires + +Args: state -- pine state pointer + q1 -- Line to prompt on + + Returns 0 if it was cancelled, 1 otherwise. + ----*/ +int +select_sort(struct pine *state, int ql, SortOrder *sort, int *rev) +{ + char prompt[200], tmp[3], *p; + int s, i; + int deefault = 'a', retval = 1; + HelpType help; + ESCKEY_S sorts[14]; + +#ifdef _WINDOWS + DLG_SORTPARAM sortsel; + + if (mswin_usedialog ()) { + + sortsel.reverse = mn_get_revsort (state->msgmap); + sortsel.cursort = mn_get_sort (state->msgmap); + /* assumption here that HelpType is char ** */ + sortsel.helptext = h_select_sort; + sortsel.rval = 0; + + if ((retval = os_sortdialog (&sortsel))) { + *sort = sortsel.cursort; + *rev = sortsel.reverse; + } + + return (retval); + } +#endif + + /*----- String together the prompt ------*/ + tmp[1] = '\0'; + if(F_ON(F_USE_FK,ps_global)) + strncpy(prompt, _("Choose type of sort : "), sizeof(prompt)); + else + strncpy(prompt, _("Choose type of sort, or 'R' to reverse current sort : "), + sizeof(prompt)); + + for(i = 0; state->sort_types[i] != EndofList; i++) { + sorts[i].rval = i; + p = sorts[i].label = sort_name(state->sort_types[i]); + while(*(p+1) && islower((unsigned char)*p)) + p++; + + sorts[i].ch = tolower((unsigned char)(tmp[0] = *p)); + sorts[i].name = cpystr(tmp); + + if(mn_get_sort(state->msgmap) == state->sort_types[i]) + deefault = sorts[i].rval; + } + + sorts[i].ch = 'r'; + sorts[i].rval = 'r'; + sorts[i].name = cpystr("R"); + if(F_ON(F_USE_FK,ps_global)) + sorts[i].label = N_("Reverse"); + else + sorts[i].label = ""; + + sorts[++i].ch = -1; + help = h_select_sort; + + if((F_ON(F_USE_FK,ps_global) + && ((s = double_radio_buttons(prompt,ql,sorts,deefault,'x', + help,RB_NORM)) != 'x')) + || + (F_OFF(F_USE_FK,ps_global) + && ((s = radio_buttons(prompt,ql,sorts,deefault,'x', + help,RB_NORM)) != 'x'))){ + state->mangled_body = 1; /* signal screen's changed */ + if(s == 'r') + *rev = !mn_get_revsort(state->msgmap); + else + *sort = state->sort_types[s]; + + if(F_ON(F_SHOW_SORT, ps_global)) + ps_global->mangled_header = 1; + } + else{ + retval = 0; + cmd_cancelled("Sort"); + } + + while(--i >= 0) + fs_give((void **)&sorts[i].name); + + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + return(retval); +} + + +/*--------------------------------------------------------------------- + Build list of folders in the given context for user selection + + Args: c -- pointer to pointer to folder's context context + f -- folder prefix to display + sublist -- whether or not to use 'f's contents as prefix + lister -- function used to do the actual display + + Returns: malloc'd string containing sequence, else NULL if + no messages in msgmap with local "selected" flag. + ----*/ +int +display_folder_list(CONTEXT_S **c, char *f, int sublist, int (*lister) (struct pine *, CONTEXT_S **, char *, int)) +{ + int rc; + CONTEXT_S *tc; + void (*redraw)(void) = ps_global->redrawer; + + push_titlebar_state(); + tc = *c; + if((rc = (*lister)(ps_global, &tc, f, sublist)) != 0) + *c = tc; + + ClearScreen(); + pop_titlebar_state(); + redraw_titlebar(); + if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */ + (*ps_global->redrawer)(); + + if(rc == 1 && F_ON(F_SELECT_WO_CONFIRM, ps_global)) + return(1); + + return(0); +} + + +/* + * Allow user to choose a single item from a list of strings. + * + * Args list -- Array of strings to choose from, NULL terminated. + * displist -- Array of strings to display instead of displaying list. + * Indices correspond to the list array. Display the displist + * but return the item from list if displist non-NULL. + * title -- For conf_scroll_screen + * pdesc -- For conf_scroll_screen + * help -- For conf_scroll_screen + * htitle -- For conf_scroll_screen + * + * Returns an allocated copy of the chosen item or NULL. + */ +char * +choose_item_from_list(char **list, char **displist, char *title, char *pdesc, HelpType help, + char *htitle, char *cursor_location) +{ + LIST_SEL_S *listhead, *ls, *p, *starting_val = NULL; + char **t, **dl; + char *ret = NULL, *choice = NULL; + + /* build the LIST_SEL_S list */ + p = listhead = NULL; + for(t = list, dl = displist; *t; t++, dl++){ + ls = (LIST_SEL_S *) fs_get(sizeof(*ls)); + memset(ls, 0, sizeof(*ls)); + ls->item = cpystr(*t); + if(displist) + ls->display_item = cpystr(*dl); + + if(cursor_location && (cursor_location == (*t))) + starting_val = ls; + + if(p){ + p->next = ls; + p = p->next; + } + else + listhead = p = ls; + } + + if(!listhead) + return(ret); + + if(!select_from_list_screen(listhead, SFL_NONE, title, pdesc, + help, htitle, starting_val)) + for(p = listhead; !choice && p; p = p->next) + if(p->selected) + choice = p->item; + + if(choice) + ret = cpystr(choice); + + free_list_sel(&listhead); + + return(ret); +} + + +void +free_list_sel(LIST_SEL_S **lsel) +{ + if(lsel && *lsel){ + free_list_sel(&(*lsel)->next); + if((*lsel)->item) + fs_give((void **) &(*lsel)->item); + + if((*lsel)->display_item) + fs_give((void **) &(*lsel)->display_item); + + fs_give((void **) lsel); + } +} + + +/* + * file_lister - call pico library's file lister + */ +int +file_lister(char *title, char *path, size_t pathlen, char *file, size_t filelen, int newmail, int flags) +{ + PICO pbf; + int rv; + void (*redraw)(void) = ps_global->redrawer; + + standard_picobuf_setup(&pbf); + push_titlebar_state(); + if(!newmail) + pbf.newmail = NULL; + +/* BUG: what about help command and text? */ + pbf.pine_anchor = title; + + rv = pico_file_browse(&pbf, path, pathlen, file, filelen, NULL, 0, flags); + standard_picobuf_teardown(&pbf); + fix_windsize(ps_global); + init_signals(); /* has it's own signal stuff */ + + /* Restore display's titlebar and body */ + pop_titlebar_state(); + redraw_titlebar(); + if((ps_global->redrawer = redraw) != NULL) + (*ps_global->redrawer)(); + + return(rv); +} + + +/*---------------------------------------------------------------------- + Print current folder index + + ---*/ +int +print_index(struct pine *state, MSGNO_S *msgmap, int agg) +{ + long i; + ICE_S *ice; + char buf[MAX_SCREEN_COLS+1]; + + for(i = 1L; i <= mn_get_total(msgmap); i++){ + if(agg && !get_lflag(state->mail_stream, msgmap, i, MN_SLCT)) + continue; + + if(!agg && msgline_hidden(state->mail_stream, msgmap, i, 0)) + continue; + + ice = build_header_line(state, state->mail_stream, msgmap, i, NULL); + + if(ice){ + /* + * I don't understand why we'd want to mark the current message + * instead of printing out the first character of the status + * so I'm taking it out and including the first character of the + * line instead. Hubert 2006-02-09 + * + if(!print_char((mn_is_cur(msgmap, i)) ? '>' : ' ')) + return(0); + */ + + if(!gf_puts(simple_index_line(buf,sizeof(buf),ice,i), + print_char) + || !gf_puts(NEWLINE, print_char)) + return(0); + } + } + + return(1); +} + + +#ifdef _WINDOWS + +/* + * windows callback to get/set header mode state + */ +int +header_mode_callback(set, args) + int set; + long args; +{ + return(ps_global->full_header); +} + + +/* + * windows callback to get/set zoom mode state + */ +int +zoom_mode_callback(set, args) + int set; + long args; +{ + return(any_lflagged(ps_global->msgmap, MN_HIDE) != 0); +} + + +/* + * windows callback to get/set zoom mode state + */ +int +any_selected_callback(set, args) + int set; + long args; +{ + return(any_lflagged(ps_global->msgmap, MN_SLCT) != 0); +} + + +/* + * + */ +int +flag_callback(set, flags) + int set; + long flags; +{ + MESSAGECACHE *mc; + int newflags = 0; + long msgno; + int permflag = 0; + + switch (set) { + case 1: /* Important */ + permflag = ps_global->mail_stream->perm_flagged; + break; + + case 2: /* New */ + permflag = ps_global->mail_stream->perm_seen; + break; + + case 3: /* Answered */ + permflag = ps_global->mail_stream->perm_answered; + break; + + case 4: /* Deleted */ + permflag = ps_global->mail_stream->perm_deleted; + break; + + } + + if(!(any_messages(ps_global->msgmap, NULL, "to Flag") + && can_set_flag(ps_global, "flag", permflag))) + return(0); + + if(sp_io_error_on_stream(ps_global->mail_stream)){ + sp_set_io_error_on_stream(ps_global->mail_stream, 0); + pine_mail_check(ps_global->mail_stream); /* forces write */ + return(0); + } + + msgno = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap)); + if(msgno > 0L && ps_global->mail_stream + && msgno <= ps_global->mail_stream->nmsgs + && (mc = mail_elt(ps_global->mail_stream, msgno)) + && mc->valid){ + /* + * NOTE: code below is *VERY* sensitive to the order of + * the messages defined in resource.h for flag handling. + * Don't change it unless you know what you're doing. + */ + if(set){ + char *flagstr; + long mflag; + + switch(set){ + case 1 : /* Important */ + flagstr = "\\FLAGGED"; + mflag = (mc->flagged) ? 0L : ST_SET; + break; + + case 2 : /* New */ + flagstr = "\\SEEN"; + mflag = (mc->seen) ? 0L : ST_SET; + break; + + case 3 : /* Answered */ + flagstr = "\\ANSWERED"; + mflag = (mc->answered) ? 0L : ST_SET; + break; + + case 4 : /* Deleted */ + flagstr = "\\DELETED"; + mflag = (mc->deleted) ? 0L : ST_SET; + break; + + default : /* bogus */ + return(0); + } + + mail_flag(ps_global->mail_stream, long2string(msgno), + flagstr, mflag); + + if(ps_global->redrawer) + (*ps_global->redrawer)(); + } + else{ + /* Important */ + if(mc->flagged) + newflags |= 0x0001; + + /* New */ + if(!mc->seen) + newflags |= 0x0002; + + /* Answered */ + if(mc->answered) + newflags |= 0x0004; + + /* Deleted */ + if(mc->deleted) + newflags |= 0x0008; + } + } + + return(newflags); +} + + + +/* + * BUG: Should teach this about keywords + */ +MPopup * +flag_submenu(mc) + MESSAGECACHE *mc; +{ + static MPopup flag_submenu[] = { + {tMessage, {N_("Important"), lNormal}, {IDM_MI_FLAGIMPORTANT}}, + {tMessage, {N_("New"), lNormal}, {IDM_MI_FLAGNEW}}, + {tMessage, {N_("Answered"), lNormal}, {IDM_MI_FLAGANSWERED}}, + {tMessage , {N_("Deleted"), lNormal}, {IDM_MI_FLAGDELETED}}, + {tTail} + }; + + /* Important */ + flag_submenu[0].label.style = (mc && mc->flagged) ? lChecked : lNormal; + + /* New */ + flag_submenu[1].label.style = (mc && mc->seen) ? lNormal : lChecked; + + /* Answered */ + flag_submenu[2].label.style = (mc && mc->answered) ? lChecked : lNormal; + + /* Deleted */ + flag_submenu[3].label.style = (mc && mc->deleted) ? lChecked : lNormal; + + return(flag_submenu); +} + +#endif /* _WINDOWS */ |