#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: addrbook.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2006-2009 University of Washington * Copyright 2013-2021 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 * * ======================================================================== */ /*====================================================================== addrbook.c display, browse and edit the address book. ====*/ #include "headers.h" #include "addrbook.h" #include "adrbkcmd.h" #include "mailindx.h" #include "mailcmd.h" #include "status.h" #include "confscroll.h" #include "keymenu.h" #include "radio.h" #include "titlebar.h" #include "help.h" #include "folder.h" #include "signal.h" #include "alpine.h" #include "../pith/adrbklib.h" #include "../pith/abdlc.h" #include "../pith/addrbook.h" #include "../pith/ablookup.h" #include "../pith/addrstring.h" #include "../pith/bldaddr.h" #include "../pith/state.h" #include "../pith/bitmap.h" #include "../pith/newmail.h" #include "../pith/remote.h" #include "../pith/util.h" #include "../pith/thread.h" #include "../pith/hist.h" #include "../pith/list.h" HelpType gAbookHelp = NO_HELP; /* internal prototypes */ void end_adrbks(void); int init_disp_form_prefix(PerAddrBook *, int *); void display_book(int, int, int, int, Pos *); void paint_line(int, long, int, Pos *); void redraw_addr_screen(void); char *addr_book(AddrBookArg, char *, char **); void ab_zoom(EXPANDED_S *, int *); void ab_unzoom(int *); void empty_warning(long); void clickable_warning(long); void no_tabs_warning(void); int ab_apply_cmd(struct pine *, AdrBk *, long, int); void ab_select(struct pine *, AdrBk *, long, int, int *); int ab_select_type(AdrBk *, int); int ab_select_text(AdrBk *, int); int match_check(AdrBk_Entry *, int, char *); void ab_goto_folder(int); long ab_whereis(int *, int); int any_addrs_avail(long); int entry_is_clickable(long); int entry_is_clickable_title(long); int is_empty(long); int entry_is_listent(long); int entry_is_addkey(long); int entry_is_askserver(long); int add_is_global(long); int next_selectable_line(long, long *); int prev_selectable_line(long, long *); void erase_checks(void); int search_book(long, int, long *, int *, int *); int find_in_book(long, char *, long *, int *); int search_in_one_line(AddrScrn_Disp *, AdrBk_Entry *, char *, char *); int abook_select_tool(struct pine *, int, CONF_S **, unsigned); char *choose_an_address_with_this_prefix(COMPLETE_S *); #ifdef _WINDOWS int addr_scroll_up(long); int addr_scroll_down(long); int addr_scroll_to_pos(long); int addr_scroll_callback(int, long); char *pcpine_help_addrbook(char *); #endif /* TRANSLATORS: This is a placeholder for a list of addresses in address book */ #define CLICKHERE _("[ Address List ]") /* TRANSLATORS: This is an empty address list */ #define EMPTY _("[ Empty ]") #define ZOOM_EMPTY _("[ No Selected Entries in this Address Book ]") /* TRANSLATORS: Move here means to move the cursor to this line of the screen */ #define ADD_PERSONAL _(" [ Move here to add a Personal Address Book ]") /* TRANSLATORS: A global address book is a shared address book */ #define ADD_GLOBAL _(" [ Move here to add a Global Address Book ]") /* TRANSLATORS: A heading for an address book distribution list */ #define DISTLIST _("DISTRIBUTION LIST:") #define NOABOOKS _("[ No Address Book Configured ]") /* TRANSLATORS: Select here means to move the cursor to this line and then type the Select command. */ #define CLICKHERECMB _("[ Select Here to See Expanded List ]") /* * Returns the index of the current address book. */ int cur_addr_book(void) { return(adrbk_num_from_lineno(as.top_ent + as.cur_row)); } /* * Returns 1 if current abook is open, else 0. */ int cur_is_open(void) { int current, ret = 0; if(as.initialized && as.n_addrbk > 0 && (current = cur_addr_book()) >= 0 && current < as.n_addrbk && !entry_is_askserver(as.top_ent+as.cur_row)) ret = (as.adrbks[current].ostatus == Open); return(ret); } void end_adrbks(void) { int i; dprint((2, "- end_adrbks -\n")); if(!as.initialized) return; for(i = 0; i < as.n_addrbk; i++) init_abook(&as.adrbks[i], Closed); as.selections = 0; as.checkboxes = 0; ab_nesting_level = 0; } /* * We have to call this to set up the format of the columns. There is a * separate format for each addrbook, so we need to call this for each * addrbook. We call it when the pab's are built. It also depends on * whether or not as.checkboxes is set, so if we go into a Select mode * from the address book maintenance screen we need to re-call this. Since * we can't go back out of ListMode we don't have that problem. Restore_state * has to call it because of the as.checkboxes possibly being different in * the two states. */ void init_disp_form(PerAddrBook *pab, char **list, int addrbook_num) { addrbook_new_disp_form(pab, list, addrbook_num, init_disp_form_prefix); } int init_disp_form_prefix(PerAddrBook *pab, int *columnp) { int rv = 0; if(as.checkboxes){ pab->disp_form[(*columnp)].wtype = Fixed; pab->disp_form[(*columnp)].req_width = 3; pab->disp_form[(*columnp)++].type = Checkbox; } else if(as.selections){ if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global) && StartBold()){ EndBold(); rv = 1; } else{ pab->disp_form[(*columnp)].wtype = Fixed; pab->disp_form[(*columnp)].req_width = 1; pab->disp_form[(*columnp)++].type = Selected; } } return(rv); } /* * save_and_restore - single interface to save_state and restore_state */ void save_and_restore(int cmd, SAVE_STATE_S *state) { switch(cmd){ case SAR_SAVE : save_state(state); break; case SAR_RESTORE : restore_state(state); break; } } /* * Save the screen state and the Open or Closed status of the addrbooks. */ void save_state(SAVE_STATE_S *state) { int i; DL_CACHE_S *dlc; dprint((9, "- save_state -\n")); /* allocate space for saving the screen structure and save it */ state->savep = (AddrScrState *)fs_get(sizeof(AddrScrState)); *(state->savep) = as; /* copy the struct */ if(as.initialized){ /* allocate space for saving the ostatus for each addrbook */ state->stp = (OpenStatus *)fs_get(as.n_addrbk * sizeof(OpenStatus)); for(i = 0; i < as.n_addrbk; i++) (state->stp)[i] = as.adrbks[i].ostatus; state->dlc_to_warp_to = (DL_CACHE_S *)fs_get(sizeof(DL_CACHE_S)); dlc = get_dlc(as.top_ent + as.cur_row); *(state->dlc_to_warp_to) = *dlc; /* copy the struct */ } } /* * Restore the state. * * Side effect: Flushes addrbook entry cache entries so they need to be * re-fetched afterwords. This only applies to entries obtained since * the call to save_state. * Also flushes all dlc cache entries, so dlist calls need to be repeated. */ void restore_state(SAVE_STATE_S *state) { int i; dprint((9, "- restore_state -\n")); as = *(state->savep); /* put back cur_row and all that */ if(as.initialized){ /* restore addressbook OpenStatus to what it was before */ for(i = 0; i < as.n_addrbk; i++){ init_disp_form(&as.adrbks[i], ps_global->VAR_ABOOK_FORMATS, i); init_abook(&as.adrbks[i], (state->stp)[i]); } /* * jump cache back to where we were */ warp_to_dlc(state->dlc_to_warp_to, as.top_ent+as.cur_row); fs_give((void **)&state->dlc_to_warp_to); fs_give((void **)&state->stp); } if(state->savep) fs_give((void **)&state->savep); } /* * Returns the addrbook entry for this display row. */ AdrBk_Entry * ae(long int row) { PerAddrBook *pab; LineType type; AddrScrn_Disp *dl; dl = dlist(row); type = dl->type; if(!(type == Simple || type == ListHead || type == ListEnt || type == ListClickHere)) return((AdrBk_Entry *)NULL); pab = &as.adrbks[adrbk_num_from_lineno(row)]; return(adrbk_get_ae(pab->address_book, (a_c_arg_t) dl->elnum)); } /* * Args: start_disp -- line to start displaying on when redrawing, 0 is * the top_of_screen * cur_line -- current line number (0 is 1st line we display) * old_line -- old line number * redraw -- flag requesting redraw as opposed to update of * current line * start_pos -- return position where highlighted text begins here * * Result: lines painted on the screen * * It either redraws the screen from line "start_disp" down or * moves the cursor from one field to another. */ void display_book(int start_disp, int cur_line, int old_line, int redraw, Pos *start_pos) { int screen_row, highlight; long global_row; Pos sp; dprint((9, "- display_book() -\n top %d start %d cur_line %d old_line %d redraw %d\n", as.top_ent, start_disp, cur_line, old_line, redraw)); if(start_pos){ start_pos->row = 0; start_pos->col = 0; } if(as.l_p_page <= 0) return; #ifdef _WINDOWS mswin_beginupdate(); #endif if(redraw){ /*--- Repaint all of the screen or bottom part of screen ---*/ global_row = as.top_ent + start_disp; for(screen_row = start_disp; screen_row < as.l_p_page; screen_row++, global_row++){ highlight = (screen_row == cur_line); ClearLine(screen_row + HEADER_ROWS(ps_global)); paint_line(screen_row + HEADER_ROWS(ps_global), global_row, highlight, &sp); if(start_pos && highlight) *start_pos = sp; } } else{ /*--- Only update current, or move the cursor ---*/ if(cur_line != old_line){ /*--- Repaint old position to erase "cursor" ---*/ if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global)) ClearLine(old_line + HEADER_ROWS(ps_global)); paint_line(old_line + HEADER_ROWS(ps_global), as.top_ent + old_line, 0, &sp); } /*--- paint the position with the cursor ---*/ if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global)) ClearLine(cur_line + HEADER_ROWS(ps_global)); paint_line(cur_line + HEADER_ROWS(ps_global), as.top_ent + cur_line, 1, &sp); if(start_pos) *start_pos = sp; } #ifdef _WINDOWS scroll_setpos(as.top_ent); mswin_endupdate(); #endif fflush(stdout); } /* * Paint a line on the screen * * Args: line -- Line on screen to paint * global_row -- Row number of line to paint * highlight -- Line should be highlighted * start_pos -- return position where text begins here * * Result: Line is painted * * The three field widths for the formatting are passed in. There is an * implicit 2 spaces between the fields. * * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ... */ void paint_line(int line, long int global_row, int highlight, Pos *start_pos) { int scol, bolden = 0; char *start_hilite_here, *end_hilite_here; AddrScrn_Disp *dl; char lbuf[6*MAX_SCREEN_COLS + 1]; register char *p; dprint((10, "- paint_line(%d, %d) -\n", line, highlight)); dl = dlist(global_row); start_pos->row = line; start_pos->col = 0; /* default */ switch(dl->type){ case Beginning: case End: return; default: break; } p = get_abook_display_line(global_row, line == HEADER_ROWS(ps_global), highlight ? &start_hilite_here : NULL, highlight ? &end_hilite_here : NULL, &scol, lbuf, sizeof(lbuf)); if(as.selections && as.do_bold && (dl->type == ListHead || dl->type == Simple)){ PerAddrBook *pab; pab = &as.adrbks[adrbk_num_from_lineno(global_row)]; if(entry_is_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum)){ bolden++; StartBold(); } } if(p && *p){ if(highlight){ char save_char; MoveCursor(line, 0); /* * print part before highlight starts */ if(start_hilite_here != NULL){ save_char = *start_hilite_here; *start_hilite_here = '\0'; Write_to_screen(p); *start_hilite_here = save_char; } /* * print highlighted part */ if(end_hilite_here != NULL){ save_char = *end_hilite_here; *end_hilite_here = '\0'; } StartInverse(); Write_to_screen(start_hilite_here ? start_hilite_here : p); EndInverse(); /* * print part after highlight ends */ if(end_hilite_here != NULL){ *end_hilite_here = save_char; Write_to_screen(end_hilite_here); } } else PutLine0(line, 0, p); } if(bolden) EndBold(); if(scol > 0) start_pos->col = scol; } /* * Assemble a line suitable for displaying on screen * * Args: global_row -- Row number of line to assemble. * continuation -- This is the top line of screen display * s_hilite -- Location in the returned line where highlight * should start, if desired * e_hilite -- Location in the returned line where highlight * should end, if desired * retcol -- Return column where text begins here. * lbuf -- Put the output here. Lbuf should be at least * size 6*screen_cols+1. * lbufsize -- Size of lbuf array * * Result: Pointer to lbuf is returned. * * There is an implicit 2 spaces between the fields. * * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ... */ char * get_abook_display_line(long int global_row, int continuation, char **s_hilite, char **e_hilite, int *retcol, char *lbuf, size_t lbufsize) { int fld_col[NFIELDS], fld_width[NFIELDS], screen_width, width, col, special_col = 0, width_consumed, scol = -1, fld; char *string, *writeptr; char special[6*MAX_SCREEN_COLS-1]; AddrScrn_Disp *dl; AdrBk_Entry *abe; PerAddrBook *pab; #define LSPACE() (lbufsize - (writeptr - lbuf) - 1) dprint((10, "- get_display_line(%d) -\n", global_row)); dl = dlist(global_row); if(retcol) *retcol = 0; /* default */ if(s_hilite){ *s_hilite = NULL; /* NULL in these means to highlight whole line */ *e_hilite = NULL; } writeptr = lbuf; memset(writeptr, 0, lbufsize); memset(special, 0, sizeof(special)); switch(dl->type){ case Beginning: case End: return lbuf; default: break; } screen_width = ps_global->ttyo->screen_cols; /* the types in this set span all columns */ switch(dl->type){ /* center these */ case Text: case Title: case TitleCmb: case ClickHereCmb: case Empty: case ZoomEmpty: case NoAbooks: if(dl->type == Empty) string = EMPTY; else if(dl->type == ZoomEmpty) string = ZOOM_EMPTY; else if(dl->type == NoAbooks) string = NOABOOKS; else if(dl->type == ClickHereCmb) string = CLICKHERECMB; else string = dl->usst; /* center it */ col = (screen_width - (int) utf8_width(string))/2; col = MAX(col, 0); width_consumed = 0; /* col spaces to start */ if(col > 0 && LSPACE() >= col){ memset(writeptr, ' ', col); writeptr += col; width_consumed += col; } if((width=utf8_width(string)) <= screen_width-col){ strncpy(writeptr, string, LSPACE()); width_consumed += width; } else{ utf8_pad_to_width(writeptr, string, LSPACE(), screen_width-col, 1); width_consumed = screen_width; } if(s_hilite && *s_hilite == NULL){ *s_hilite = writeptr; *e_hilite = writeptr + strlen(writeptr); } writeptr += strlen(writeptr); if(width_consumed < screen_width) memset(writeptr, ' ', screen_width-width_consumed); if(retcol) *retcol = col; return lbuf; /* left adjust these */ case AddFirstPers: case AddFirstGlob: case AskServer: if(dl->type == AddFirstPers) string = ADD_PERSONAL; else if(dl->type == AddFirstGlob) string = ADD_GLOBAL; else string = dl->usst; /* left adjust it */ col = 0; width_consumed = 0; if((width=utf8_width(string)) <= screen_width){ strncpy(writeptr, string, LSPACE()); width_consumed += width; } else{ utf8_pad_to_width(writeptr, string, LSPACE(), screen_width, 1); width_consumed = screen_width; } if(s_hilite && *s_hilite == NULL){ *s_hilite = writeptr; *e_hilite = writeptr + strlen(writeptr); } writeptr += strlen(writeptr); if(width_consumed < screen_width) memset(writeptr, ' ', screen_width-width_consumed); if(retcol) *retcol = col; return lbuf; default: break; } pab = &as.adrbks[adrbk_num_from_lineno(global_row)]; for(fld = 0; fld < NFIELDS; fld++) fld_width[fld] = pab->disp_form[fld].width; fld_col[0] = 0; for(fld = 1; fld < NFIELDS; fld++) fld_col[fld] = MIN(fld_col[fld-1]+fld_width[fld-1]+2, screen_width); width_consumed = 0; /* fill in the fields */ for(fld = 0; fld < NFIELDS; fld++){ if(fld_width[fld] == 0 && !(pab->disp_form[fld].type == Addr && (dl->type == ListClickHere || dl->type == ListEmpty))) continue; switch(pab->disp_form[fld].type){ case Notused: break; case Nickname: switch(dl->type){ case Simple: case ListHead: abe = ae(global_row); string = (abe && abe->nickname) ? abe->nickname : ""; if(scol == -1) scol = fld_col[fld]; /* left adjust string in field */ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; default: break; } break; case Fullname: switch(dl->type){ case Simple: case ListHead: abe = ae(global_row); string = (abe && abe->fullname) ? abe->fullname : ""; if(scol == -1) scol = fld_col[fld]; /* left adjust string in field */ utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; case ListEnt: /* continuation line */ if(continuation){ int width1, width2; width2 = MIN(11, fld_width[fld]); width1 = MAX(fld_width[fld] - width2 - 1, 0); abe = ae(global_row); string = (abe && abe->fullname) ? abe->fullname : ""; if(width1){ utf8_pad_to_width(writeptr, string, LSPACE(), width1, 1); writeptr += strlen(writeptr); if(LSPACE() > 0) *writeptr++ = ' '; } /* TRANSLATORS: continuation line meaning there is more on the next page */ if(width2 && LSPACE() >= strlen(_("(continued)"))){ strncpy(writeptr, _("(continued)"), width2); lbuf[lbufsize-1] = '\0'; writeptr += strlen(writeptr); } width_consumed += fld_width[fld]; lbuf[lbufsize-1] = '\0'; } break; default: break; } break; case Addr: switch(dl->type){ case ListClickHere: case ListEmpty: if(dl->type == ListClickHere) string = CLICKHERE; else string = EMPTY; if((width=utf8_width(string)) <= fld_width[fld]){ strncpy(writeptr, string, LSPACE()); if(s_hilite && *s_hilite == NULL){ *s_hilite = writeptr; *e_hilite = writeptr + strlen(writeptr); } writeptr += strlen(writeptr); width_consumed += width; if(scol == -1) scol = fld_col[fld]; } else{ /* * Place the string in special array and overlay it * onto the right edge of the screen at the end. */ if(width <= screen_width){ strncpy(special, string, sizeof(special)); special_col = screen_width - width; } else{ utf8_pad_to_width(special, string, sizeof(special), screen_width, 1); special_col = 0; } special[sizeof(special)-1] = '\0'; } break; case ListHead: if(scol == -1) scol = fld_col[fld]; string = DISTLIST; utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; case Simple: abe = ae(global_row); #ifdef ENABLE_LDAP if(abe && abe->addr.addr && !strncmp(abe->addr.addr, QRUN_LDAP, LEN_QRL)) string = LDAP_DISP; else #endif string = (abe && abe->tag == Single && abe->addr.addr) ? abe->addr.addr : ""; if(scol == -1) scol = fld_col[fld]; utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; case ListEnt: string = listmem(global_row) ? listmem(global_row) : ""; if(scol == -1) scol = fld_col[fld]; utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); if(s_hilite && *s_hilite == NULL){ *s_hilite = writeptr; *e_hilite = writeptr + strlen(writeptr); } writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; default: break; } break; case Filecopy: case Comment: switch(dl->type){ case Simple: case ListHead: abe = ae(global_row); if(pab->disp_form[fld].type == Filecopy) string = (abe && abe->fcc) ? abe->fcc : ""; else string = (abe && abe->extra) ? abe->extra : ""; if(scol == -1) scol = fld_col[fld]; utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; default: break; } break; case Checkbox: switch(dl->type){ case Simple: case ListHead: if(entry_is_checked(pab->address_book->checks, (a_c_arg_t)dl->elnum)) string = "[X]"; else string = "[ ]"; if(scol == -1) scol = fld_col[fld]; utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; default: break; } break; case Selected: switch(dl->type){ case Simple: case ListHead: if(entry_is_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum)) string = "X"; else string = " "; if(scol == -1) scol = fld_col[fld]; utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1); writeptr += strlen(writeptr); width_consumed += fld_width[fld]; break; default: break; } break; case WhenNoAddrDisplayed: switch(dl->type){ case ListClickHere: case ListEmpty: case ListEnt: if(dl->type == ListClickHere) string = CLICKHERE; else if(dl->type == ListEmpty) string = EMPTY; else string = listmem(global_row) ? listmem(global_row) : ""; if((width=utf8_width(string)) <= fld_width[fld]) strncpy(special, string, sizeof(special)); else utf8_pad_to_width(special, string, sizeof(special), fld_width[fld], 1); special[sizeof(special)-1] = '\0'; special_col = screen_width - fld_width[fld]; special_col = MAX(0, special_col); break; default: break; } break; default: break; } if(fld < NFIELDS-1) while(width_consumed < fld_col[fld+1] && LSPACE() > 0){ *writeptr++ = ' '; width_consumed++; } } /* * Right adjust the special string in lbuf, writing over * anything in our way. */ if(special[0]){ unsigned got_width; writeptr = utf8_count_forw_width(lbuf, special_col, &got_width); strncpy(writeptr, special, LSPACE()); if(scol == -1) scol = got_width; if(s_hilite && *s_hilite == NULL){ *s_hilite = writeptr; *e_hilite = writeptr + strlen(writeptr); } writeptr += strlen(writeptr); width_consumed = (int) utf8_width(lbuf); while(width_consumed++ < screen_width && LSPACE() > 0) *writeptr++ = ' '; if(LSPACE() > 0) *writeptr = '\0'; } if(scol > 0 && retcol) *retcol = scol; lbuf[lbufsize-1] = '\0'; return lbuf; } /* * Set field widths for the columns of the display. The idea is to * try to come up with something that works pretty well. Getting it just * right isn't important. * * Col1 and col2 are arrays which contain some widths useful for * formatting the screen. The 0th element is the max * width in that column. The 1st element is the max of the third largest * width in each addressbook (yup, strange). * * The info above applies to the default case (AllAuto). The newer methods * where the user specifies the format is the else part of the big if and * is quite a bit different. It's all sort of ad hoc when we're asked * to calculate one of the fields for the user. * * Returns non-zero if the widths changed since the last time called. */ int calculate_field_widths(void) { int space_left, i, j, screen_width; int ret = 0; PerAddrBook *pab; WIDTH_INFO_S *widths; int max_nick, max_full, max_addr, third_full, third_addr, third_fcc; int col1[5], col2[5]; int nick = -1, full = -1, addr = -1; #define SEP 2 /* space between columns */ dprint((9, "- calculate_field_widths -\n")); screen_width = ps_global->ttyo->screen_cols; /* calculate widths for each addrbook independently */ for(j = 0; j < as.n_addrbk; j++){ pab = &as.adrbks[j]; max_nick = 0; max_full = 2; max_addr = 2; third_full = 2; third_addr = 2; third_fcc = 2; if(pab->address_book){ widths = &pab->address_book->widths; max_nick = MIN(MAX(max_nick, widths->max_nickname_width), 25); max_full = MAX(max_full, widths->max_fullname_width); max_addr = MAX(max_addr, widths->max_addrfield_width); third_full = MAX(third_full, widths->third_biggest_fullname_width); if(third_full == 2) third_full = MAX(third_full, 2*max_full/3); third_addr = MAX(third_addr, widths->third_biggest_addrfield_width); if(third_addr == 2) third_addr = MAX(third_addr, 2*max_addr/3); third_fcc = MAX(third_fcc, widths->third_biggest_fccfield_width); if(third_fcc == 2) third_fcc = MAX(third_fcc, 2*widths->max_fccfield_width/3); } /* figure out which order they're in and reset widths */ for(i = 0; i < NFIELDS; i++){ pab->disp_form[i].width = 0; switch(pab->disp_form[i].type){ case Nickname: nick = i; break; case Fullname: full = i; break; case Addr: addr = i; break; default: break; } } /* Compute default format */ if(pab->disp_form[1].wtype == AllAuto){ col1[0] = max_full; col2[0] = max_addr; col1[1] = third_full; col2[1] = third_addr; col1[2] = 3; col2[2] = 3; col1[3] = 2; col2[3] = 2; col1[4] = 1; col2[4] = 1; space_left = screen_width; if(pab->disp_form[0].type == Selected || pab->disp_form[0].type == Checkbox){ pab->disp_form[0].width = MIN(pab->disp_form[0].req_width,space_left); space_left = MAX(space_left-(pab->disp_form[0].width + SEP), 0); } /* * All of the nickname field should be visible, * and make it at least 3. */ pab->disp_form[nick].width = MIN(MAX(max_nick, 3), space_left); /* * The SEP is for two blank columns between nickname and next field. * Those blank columns are taken automatically in paint_line(). */ space_left -= (pab->disp_form[nick].width + SEP); if(space_left > 0){ for(i = 0; i < 5; i++){ /* try fitting most of each field in if possible */ if(col1[i] + SEP + col2[i] <= space_left){ int extra; extra = space_left - col1[i] - SEP - col2[i]; /* * try to stabilize nickname column shifts * so that screen doesn't jump around when we make changes */ if(i == 0 && pab->disp_form[nick].width < 7 && extra >= (7 - pab->disp_form[nick].width)){ extra -= (7 - pab->disp_form[nick].width); space_left -= (7 - pab->disp_form[nick].width); pab->disp_form[nick].width = 7; } pab->disp_form[addr].width = col2[i] + extra/2; pab->disp_form[full].width = space_left - SEP - pab->disp_form[addr].width; break; } } /* * None of them would fit. Toss addr field. */ if(i == 5){ pab->disp_form[full].width = space_left; pab->disp_form[addr].width = 0; } } else{ pab->disp_form[full].width = 0; pab->disp_form[addr].width = 0; } dprint((10, "Using %s choice: %d %d %d", enth_string(i+1), pab->disp_form[nick].width, pab->disp_form[full].width, pab->disp_form[addr].width)); } else{ /* non-default case */ int some_to_calculate = 0; int columns = 0; int used = 0; int avail_screen; int all_percents = 1; int pc_tot; /* * First count how many fields there are. * Fill in all the Fixed's while we're at it. */ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(pab->disp_form[i].wtype == Fixed){ pab->disp_form[i].width = pab->disp_form[i].req_width; all_percents = 0; } else if(pab->disp_form[i].wtype == WeCalculate){ pab->disp_form[i].width = pab->disp_form[i].req_width; /* for now */ some_to_calculate++; all_percents = 0; } if(pab->disp_form[i].wtype != Special){ used += pab->disp_form[i].width; columns++; } } used += ((columns-1) * SEP); avail_screen = screen_width - used; /* * Now that we know how much space we've got, we can * calculate the Percent columns. */ if(avail_screen > 0){ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(pab->disp_form[i].wtype == Percent){ /* The 2, 200, and +100 are because we're rounding */ pab->disp_form[i].width = ((2*pab->disp_form[i].req_width*avail_screen)+100) / 200; used += pab->disp_form[i].width; } } } space_left = screen_width - used; if(space_left < 0){ /* * If they're all percentages, and the percentages add up to 100, * then we should fix the rounding problem. */ pc_tot = 0; if(all_percents){ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++) if(pab->disp_form[i].wtype == Percent) pc_tot += pab->disp_form[i].req_width; } /* fix the rounding problem */ if(all_percents && pc_tot <= 100){ int col = columns; int this_col = 0; int fix = used - screen_width; while(fix--){ if(col < 0) col = columns; /* find col'th column */ for(i=0, this_col=0; i < NFIELDS; i++){ if(pab->disp_form[i].wtype == Percent){ if(col == ++this_col) break; } } pab->disp_form[i].width--; col--; } } /* * Assume they meant to have them add up to over 100%, so we * just truncate the right hand edge. */ else{ int this_fix, space_over; /* have to reduce space_over down to zero. */ space_over = used - screen_width; for(i=NFIELDS-1; i >= 0 && space_over > 0; i--){ if(pab->disp_form[i].type != Notused){ this_fix = MIN(pab->disp_form[i].width, space_over); pab->disp_form[i].width -= this_fix; space_over -= this_fix; } } } } else if(space_left > 0){ if(some_to_calculate){ /* make nickname big enough to show all nicknames */ if(nick >= 0 && pab->disp_form[nick].wtype == WeCalculate){ --some_to_calculate; if(pab->disp_form[nick].width != max_nick){ int this_fix; this_fix = MIN(max_nick-pab->disp_form[nick].width, space_left); pab->disp_form[nick].width += this_fix; space_left -= this_fix; } } if(!some_to_calculate && space_left > 0) goto none_to_calculate; if(space_left > 0){ int weight = 0; int used_wt = 0; /* add up total weight */ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(i != nick && pab->disp_form[i].wtype == WeCalculate){ switch(pab->disp_form[i].type){ case Fullname: weight += MAX(third_full, pab->disp_form[i].width); used_wt += pab->disp_form[i].width; break; case Addr: weight += MAX(third_addr, pab->disp_form[i].width); used_wt += pab->disp_form[i].width; break; case Filecopy: weight += MAX(third_fcc, pab->disp_form[i].width); used_wt += pab->disp_form[i].width; break; default: break; } } } if(weight > 0){ int this_fix; if(weight - used_wt <= space_left){ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(i != nick && pab->disp_form[i].wtype == WeCalculate){ switch(pab->disp_form[i].type){ case Fullname: this_fix = third_full - pab->disp_form[i].width; space_left -= this_fix; pab->disp_form[i].width += this_fix; break; case Addr: this_fix = third_addr - pab->disp_form[i].width; space_left -= this_fix; pab->disp_form[i].width += this_fix; break; case Filecopy: this_fix = third_fcc - pab->disp_form[i].width; space_left -= this_fix; pab->disp_form[i].width += this_fix; break; default: break; } } } /* if still space left and a comment field, all to comment */ if(space_left){ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(pab->disp_form[i].type == Comment && pab->disp_form[i].wtype == WeCalculate){ pab->disp_form[i].width += space_left; space_left = 0; } } } } else{ /* not enough space, dole out weighted pieces */ int was_sl = space_left; for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(i != nick && pab->disp_form[i].wtype == WeCalculate){ switch(pab->disp_form[i].type){ case Fullname: /* round down */ this_fix = (third_full * was_sl)/weight; space_left -= this_fix; pab->disp_form[i].width += this_fix; break; case Addr: this_fix = (third_addr * was_sl)/weight; space_left -= this_fix; pab->disp_form[i].width += this_fix; break; case Filecopy: this_fix = (third_fcc * was_sl)/weight; space_left -= this_fix; pab->disp_form[i].width += this_fix; break; default: break; } } } } } /* give out rest */ while(space_left > 0){ for(i=NFIELDS-1; i >= 0 && space_left > 0; i--){ if(i != nick && pab->disp_form[i].wtype == WeCalculate){ pab->disp_form[i].width++; space_left--; } } } } } else{ /* * If they're all percentages, and the percentages add up to 100, * then we just have to fix a rounding problem. Otherwise, we'll * assume the user meant to have them add up to less than 100. */ none_to_calculate: pc_tot = 0; if(all_percents){ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++) if(pab->disp_form[i].wtype == Percent) pc_tot += pab->disp_form[i].req_width; } if(all_percents && pc_tot >= 100){ int col = columns; int this_col = 0; int fix = screen_width - used; while(fix--){ if(col < 0) col = columns; /* find col'th column */ for(i=0, this_col=0; i < NFIELDS; i++){ if(pab->disp_form[i].wtype == Percent){ if(col == ++this_col) break; } } pab->disp_form[i].width++; col--; } } /* else, user specified less than 100%, leave it */ } } /* else space_left == zero, nothing to do */ /* * Check for special case. If we find it, this is the case where * we want to display the list entry field even though there is no * address field displayed. All of the display width is probably * used up by now, so we just need to pick some arbitrary width * for these lines. Since these lines are separate from the other * lines we've been calculating, we don't have to worry about running * into them, except for list continuation lines which we're not * going to worry about. */ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(pab->disp_form[i].wtype == Special){ pab->disp_form[i].width = MIN(utf8_width(CLICKHERE), screen_width); break; } } } /* check for width changes */ for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(pab->disp_form[i].width != pab->disp_form[i].old_width){ ret++; /* Tell the caller the screen changed */ pab->disp_form[i].old_width = pab->disp_form[i].width; } } pab->nick_is_displayed = 0; pab->full_is_displayed = 0; pab->addr_is_displayed = 0; pab->fcc_is_displayed = 0; pab->comment_is_displayed = 0; for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){ if(pab->disp_form[i].width > 0){ switch(pab->disp_form[i].type){ case Nickname: pab->nick_is_displayed++; break; case Fullname: pab->full_is_displayed++; break; case Addr: pab->addr_is_displayed++; break; case Filecopy: pab->fcc_is_displayed++; break; case Comment: pab->comment_is_displayed++; break; default: break; } } } } if(ret) dprint((9, " some widths changed\n")); return(ret); } void redraw_addr_screen(void) { dprint((7, "- redraw_addr_screen -\n")); ab_resize(); if(as.l_p_page <= 0) return; (void)calculate_field_widths(); display_book(0, as.cur_row, -1, 1, (Pos *)NULL); } /* * Little front end for address book screen so it can be called out * of the main command loop in alpine.c */ void addr_book_screen(struct pine *pine_state) { dprint((3, "\n\n --- ADDR_BOOK_SCREEN ---\n\n")); mailcap_free(); /* free resources we won't be using for a while */ if(setjmp(addrbook_changed_unexpectedly)){ /* TRANSLATORS: a warning message telling the user that the address book is being reset (re-sychronized, restarted) */ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_screen!\n")); addrbook_reset(); } ab_nesting_level = 1; /* come here only from main menu */ /* TRANSLATORS: a screen title for address book screen. Almost all of the translatable strings which are all upper case are screen titles. */ (void)addr_book(AddrBookScreen, _("ADDRESS BOOK"), NULL); end_adrbks(); pine_state->prev_screen = addr_book_screen; } void addr_book_config(struct pine *pine_state, int edit_exceptions) { if(edit_exceptions){ q_status_message(SM_ORDER, 3, 7, _("Exception Setup not implemented for address books")); return; } dprint((3, "\n\n --- ADDR_BOOK_CONFIG ---\n\n")); mailcap_free(); /* free resources we won't be using for a while */ if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_config!\n")); addrbook_reset(); } ab_nesting_level = 1; /* TRANSLATORS: A screen title */ (void)addr_book(AddrBookConfig, _("SETUP ADDRESS BOOKS"), NULL); end_adrbks(); pine_state->prev_screen = addr_book_screen; } /* * Return a single address * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_oneaddr(void) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "--- addr_book_oneaddr ---\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_oneaddr!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; /* TRANSLATORS: a screen title */ p = addr_book(SelectAddr, _("SELECT ADDRESS"), NULL); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Return a list of addresses without fullname * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_multaddr_nf(void) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "--- addr_book_multaddr_nf ---\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_multaddr_nf!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; p = addr_book(SelectMultNoFull, _("SELECT ADDRESS"), NULL); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Return a single address without fullname phrase * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_oneaddr_nf(void) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "--- addr_book_oneaddr_nf ---\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_oneaddr_nf!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; p = addr_book(SelectAddrNoFull, _("SELECT ADDRESS"), NULL); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book from message composer * * Args: error_mess -- pointer to return error messages in (unused here) * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_compose(char **error) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "--- addr_book_compose ---\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_compose!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; /* TRANSLATORS: a screen title */ p = addr_book(SelectNicksCom, _("COMPOSER: SELECT ADDRESS"), error); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book from message composer for Lcc line * * Args: error_mess -- pointer to return error messages in (unused here) * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_compose_lcc(char **error) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "--- addr_book_compose_lcc ---\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_compose_lcc!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; /* * We used to use SelectAddrLccCom here but decided it wasn't necessary * to restrict the selection to a list. */ /* TRANSLATORS: a screen title, user is composing a message and should select a distribution list from a list of addresses in the address book. */ p = addr_book(SelectNicksCom, _("COMPOSER: SELECT LIST"), error); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book from message composer for Lcc line * * Args: error_mess -- pointer to return error messages in (unused here) * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_change_list(char **error) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "--- addr_book_change_list ---\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book... addr_book_change_list!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; /* TRANSLATORS: a screen title */ p = addr_book(SelectNicksCom, _("ADDRESS BOOK (Update): SELECT ADDRESSES"), error); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book from pine_simple_send * * Returns: pointer to returned address, or NULL if nothing returned */ char * addr_book_bounce(void) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "- addr_book_bounce -\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book...addr_book_bounce!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; p = addr_book(SelectManyNicks, _("SELECT ADDRESSES"), NULL); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book from take address screen * * Returns: pointer to returned nickname, or NULL if nothing returned */ char * addr_book_takeaddr(void) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "- addr_book_takeaddr -\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book...addr_book_takeaddr!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; /* TRANSLATORS: a screen title */ p = addr_book(SelectNickTake, _("TAKEADDR: SELECT NICKNAME"), NULL); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book from editing screen for nickname field. * * Returns: pointer to returned nickname, or NULL if nothing returned */ char * addr_book_nick_for_edit(char **error) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; int save_n_serv; dprint((3, "- addr_book_nick_for_edit -\n")); save_n_serv = as.n_serv; save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book...addr_book_nick_for_edit!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; /* * This is kind of hoaky. We want to prevent the Directory Query * options from turning on when we're coming in looking for a nickname * and this seemed to be the easiest way to accomplish that. */ as.n_serv = 0; /* TRANSLATORS: a screen title, user selecting a nickname from the address book */ p = addr_book(SelectNickCom, _("SELECT NICKNAME"), error); as.n_serv = save_n_serv; if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Call address book for generic nickname select * * Returns: pointer to returned nickname, or NULL if nothing returned */ char * addr_book_selnick(void) { char *p; jmp_buf save_jmp_buf; int *save_nesting_level; dprint((3, "- addr_book_selnick -\n")); save_nesting_level = cpyint(ab_nesting_level); memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); if(setjmp(addrbook_changed_unexpectedly)){ q_status_message(SM_ORDER, 5, 10, _("Resetting address book...")); dprint((1, "RESETTING address book...addr_book_selnick!\n")); addrbook_reset(); ab_nesting_level = *save_nesting_level; } ab_nesting_level++; p = addr_book(SelectNick, _("SELECT NICKNAME"), NULL); if(ab_nesting_level <= 1) end_adrbks(); else ab_nesting_level--; memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); if(save_nesting_level) fs_give((void **)&save_nesting_level); return(p); } /* * Main address book screen * * Control loop for address book. Commands are executed out of a big * switch and screen painting is done. * The style argument controls whether or not it is called to return an address * to the composer, a nickname to the TakeAddr screen, or just for address * book maintenance. * * Args: style -- how we were called * * Return: might return a string for the composer to edit, or a nickname, ... */ char * addr_book(AddrBookArg style, char *title, char **error_message) { UCS c; int cmd, r, i, command_line, did_delete, quit, /* loop control */ km_popped, /* menu is popped up in blank menu mode */ current_changed_flag, /* only current row needs update */ was_clickable, is_clickable, was_collapsible_listent, is_collapsible_listent, was_global_config, is_global_config, was_addkey, is_addkey, was_opened, is_opened, was_custom_title, is_custom_title, setall_changed, start_disp, /* Paint from this line down (0 is top) */ rdonly, /* cur addrbook read only */ empty, /* cur addrbook empty */ are_selecting, /* called as ^T selector */ #ifdef ENABLE_LDAP directory_ok, /* called from composer, not Lcc */ #endif from_composer, /* from composer */ listmode_ok, /* ok to do ListMode with this style */ selecting_one_nick, selecting_mult_nicks, checkedn, /* how many are checked */ def_key, /* default key */ warped; /* we warped through hyperspace to a new location in the display list */ long fl, new_top_ent, /* entry on top of screen after oper */ new_line; /* new line number after operation */ char *addr; char *utf8str; bitmap_t bitmap; struct key_menu *km; OtherMenu what; PerAddrBook *pab = NULL; AddrScrn_Disp *dl; struct pine *ps; Pos cursor_pos; dprint((2, "--- addr_book --- (%s)\n", style==AddrBookScreen ? "AddrBookScreen" : style==SelectAddrLccCom ? "SelectAddrLccCom" : style==SelectNicksCom ? "SelectNicksCom" : style==SelectNick ? "SelectNick" : style==SelectNickTake ? "SelectNickTake" : style==SelectNickCom ? "SelectNickCom" : style==AddrBookConfig ? "AddrBookConfig" : style==SelectManyNicks ? "SelectManyNicks" : style==SelectAddr ? "SelectAddr" : style==SelectAddrNoFull ? "SelectAddrNoFull" : style==SelectMultNoFull ? "SelectMultNoFull": "UnknownStyle")); km = &ab_keymenu; ps = ps_global; ps->next_screen = SCREEN_FUN_NULL; from_composer = (style == SelectAddrLccCom || style == SelectNicksCom || style == SelectNickCom); are_selecting = (style != AddrBookScreen && style != AddrBookConfig); selecting_one_nick = (style == SelectNick || style == SelectNickTake || style == SelectNickCom); selecting_mult_nicks = (style == SelectAddrLccCom || style == SelectNicksCom || style == SelectManyNicks); listmode_ok = (style == SelectAddrLccCom || style == SelectNicksCom || style == SelectManyNicks || style == SelectMultNoFull); as.config = (style == AddrBookConfig); #ifdef ENABLE_LDAP directory_ok = (style == SelectNicksCom || style == AddrBookScreen || style == SelectManyNicks); #endif /* Coming in from the composer, may need to reset the window */ if(from_composer){ fix_windsize(ps); init_sigwinch(); mark_status_dirty(); mark_titlebar_dirty(); mark_keymenu_dirty(); } command_line = -FOOTER_ROWS(ps); /* third line from the bottom */ what = FirstMenu; c = 'x'; /* For display_message the first time through */ if(ps->remote_abook_validity > 0) (void)adrbk_check_and_fix_all(ab_nesting_level == 1, 0, 0); if(!init_addrbooks(HalfOpen, 1, !as.config, !are_selecting)){ if(are_selecting){ q_status_message(SM_ORDER | SM_DING, 0, 4, _("No Address Book Configured")); display_message(c); sleep(2); return NULL; } else if(!as.config){ ps->next_screen = main_menu_screen; q_status_message(SM_ORDER | SM_DING, 3, 4, _("No Address Book Configured, Use SETUP Addressbook screen")); ps->mangled_screen = 1; return NULL; } } else if(style == AddrBookScreen && as.n_addrbk == 1 && as.n_serv == 0){ if(as.adrbks[0].access == ReadOnly) q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only")); else if(as.adrbks[0].access == NoAccess) q_status_message(SM_ORDER, 0, 4, _("AddressBook not accessible, permission denied")); } if(as.l_p_page < 1){ q_status_message(SM_ORDER, 3, 3, _("Screen too small to use Address book")); return NULL; } erase_checks(); as.selections = 0; as.zoomed = 0; (void) calculate_field_widths(); quit = 0; km_popped = 0; ps->mangled_screen = 1; current_changed_flag = 0; start_disp = 0; was_clickable = 0; is_clickable = 0; was_collapsible_listent = 0; is_collapsible_listent = 0; was_global_config = 0; is_global_config = 0; was_addkey = 0; is_addkey = 0; was_opened = 0; is_opened = 0; was_custom_title = 0; is_custom_title = 0; setall_changed = 0; checkedn = 0; while(!quit){ ps->user_says_cancel = 0; if(km_popped){ km_popped--; if(km_popped == 0){ clearfooter(ps); /* * Have to repaint from earliest change down, including * at least the last two body lines. */ if(ps->mangled_body) /* it was already mangled */ start_disp = MIN(start_disp, as.l_p_page -2); else if(current_changed_flag){ ps->mangled_body = 1; start_disp = MIN(MIN(as.cur_row, as.l_p_page -2), as.old_cur_row); } else{ ps->mangled_body = 1; start_disp = as.l_p_page -2; } } } ps->redrawer = redraw_addr_screen; if(new_mail(0, NM_TIMING(c), NM_STATUS_MSG | NM_DEFER_SORT) >= 0) ps->mangled_header = 1; if(streams_died()) ps->mangled_header = 1; if(ps->mangled_screen){ ps->mangled_header = 1; ps->mangled_body = 1; ps->mangled_footer = 1; start_disp = 0; current_changed_flag = 0; ps->mangled_screen = 0; } if(ps->mangled_body){ if(calculate_field_widths()) start_disp = 0; display_book(start_disp, as.cur_row, as.old_cur_row, 1, &cursor_pos); as.old_cur_row = as.cur_row; ps->mangled_body = 0; start_disp = 0; as.cur = cur_addr_book(); pab = (as.n_addrbk && as.cur >= 0 && as.cur < as.n_addrbk && !entry_is_askserver(as.top_ent+as.cur_row)) ? &as.adrbks[as.cur] : NULL; #ifdef _WINDOWS { long i, last_sel; for(i = as.top_ent; dlist(i)->type != End; i++) next_selectable_line(i,&last_sel); as.last_ent = i; scroll_setrange(as.l_p_page, last_sel); } #endif } /* current entry has been changed */ else if(current_changed_flag){ int need_redraw; need_redraw = calculate_field_widths(); /*---------- Update the current entry, (move or change) -------*/ display_book(need_redraw ? 0 : as.cur_row, as.cur_row, as.old_cur_row, need_redraw, &cursor_pos); as.old_cur_row = as.cur_row; current_changed_flag = 0; as.cur = cur_addr_book(); pab = (as.n_addrbk && as.cur >= 0 && as.cur < as.n_addrbk && !entry_is_askserver(as.top_ent+as.cur_row)) ? &as.adrbks[as.cur] : NULL; } is_custom_title = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) && style == AddrBookScreen && as.n_addrbk > 1 && cur_is_open() && pab->abnick && pab->abnick[0]); if(( was_custom_title && !is_custom_title) || (!was_custom_title && is_custom_title)){ ps->mangled_header = 1; was_custom_title = is_custom_title; } if(ps->mangled_header){ char buf[80], *bp; if(style == AddrBookScreen){ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(as.n_addrbk > 1) snprintf(buf, sizeof(buf), _("ADDRESS BOOKS")); else snprintf(buf, sizeof(buf), _("ADDRESS BOOK")); } else /* TRANSLATORS: a screen title, the %s arguments are for a custom part of the title. Move them as a group. There are two normal cases. Either ADDRESS BOOK LIST when a list of names of address books is displayed, or ADDRESS BOOK when one is open. */ snprintf(buf, sizeof(buf), _("ADDRESS BOOK%s%s%s"), /* TRANSLATORS: This is the LIST that goes with title above */ is_custom_title ? " <" : cur_is_open() ? "" : _(" LIST"), is_custom_title ? pab->abnick : "", is_custom_title ? ">" : ""); buf[sizeof(buf)-1] = '\0'; bp = buf; } else bp = title; set_titlebar(bp, ps->mail_stream, ps->context_current, ps->cur_folder, ps->msgmap, 1, FolderName, 0, 0, NULL); ps->mangled_header = 0; } dprint((9, "addr_book: top of loop, addrbk %d top_ent %ld cur_row %d\n", as.cur, as.top_ent, as.cur_row)); /* * This is a check to catch when a move from one row to another * should cause the list of commands to change. */ is_clickable = entry_is_clickable(as.top_ent+as.cur_row); is_collapsible_listent = F_OFF(F_EXPANDED_DISTLISTS,ps) && entry_is_listent(as.top_ent+as.cur_row); is_global_config = as.config && pab && pab->type & GLOBAL; is_addkey = entry_is_addkey(as.top_ent+as.cur_row); is_opened = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) && cur_is_open()); if(( was_clickable && !is_clickable) || (!was_clickable && is_clickable) || ( was_collapsible_listent && !is_collapsible_listent) || (!was_collapsible_listent && is_collapsible_listent) || ( was_global_config && !is_global_config) || (!was_global_config && is_global_config) || ( was_addkey && !is_addkey) || (!was_addkey && is_addkey) || ( was_opened && !is_opened) || (!was_opened && is_opened) || ( setall_changed)){ ps->mangled_footer = 1; was_clickable = is_clickable; was_collapsible_listent = is_collapsible_listent; was_global_config = is_global_config; was_addkey = is_addkey; was_opened = is_opened; setall_changed = 0; } if(ps->mangled_footer){ setbitmap(bitmap); menu_clear_binding(km, '>'); menu_clear_binding(km, '.'); menu_clear_binding(km, KEY_RIGHT); menu_clear_binding(km, ctrl('M')); menu_clear_binding(km, ctrl('J')); menu_clear_binding(km, KEY_LEFT); menu_clear_binding(km, '<'); menu_clear_binding(km, ','); def_key = THREE_KEY; /* default default key */ if(as.config){ km->how_many = 1; clrbitn(OTHER_KEY, bitmap); /* TRANSLATORS: a command name for a particular key */ menu_init_binding(km, 'E', MC_EXIT, "E", N_("Exit Setup"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_EXITMODE); /* * Don't show Delete or Shuffle command if there is nothing * to delete or shuffle. */ if(is_addkey){ clrbitn(DELETE_KEY, bitmap); clrbitn(SENDTO_KEY, bitmap); clrbitn(THREE_KEY, bitmap); menu_init_binding(km, 'A', MC_ADDABOOK, "A", add_is_global(as.top_ent+as.cur_row) /* TRANSLATORS: Add Global Address book (one defined by someone else) */ ? "[" N_("Add Glob Abook") "]" /* TRANSLATORS: Add Personal Address book */ : "[" N_("Add Pers Abook") "]", ADD_KEY); def_key = ADD_KEY; } else{ /* TRANSLATORS: Delete Address book command */ menu_init_binding(km, 'D', MC_DELABOOK, "D", N_("Del Abook"), DELETE_KEY); /* TRANSLATORS: Shuffle refers to shuffling the order of things, that is, changing the order */ menu_init_binding(km, '$', MC_SHUFFLE, "$", N_("Shuffle"), SENDTO_KEY); /* TRANSLATORS: Change is a command meaning Change some item, or Edit some item to change it */ menu_init_binding(km, 'C', MC_EDITABOOK, "C", "[" N_("Change") "]", THREE_KEY); menu_init_binding(km, 'A', MC_ADDABOOK, "A", add_is_global(as.top_ent+as.cur_row) ? N_("Add Glob Abook") : N_("Add Pers Abook"), ADD_KEY); } } else if(are_selecting){ km->how_many = 1; /* * The OTHER_KEY is used as the Exit key in selection mode. * This is because the TWO_KEY is being used for < actions. */ /* TRANSLATORS: Command to Exit the Select screen. This would be a way to make no Selection and go back to where you came from. */ menu_init_binding(km, 'E', MC_EXIT, "E", N_("ExitSelect"), OTHER_KEY); KS_OSDATASET(&km->keys[OTHER_KEY], KS_EXITMODE); /* * Use the TWO_KEY for the go back key. */ if(cur_is_open() && (as.n_addrbk > 1 || as.n_serv)){ if(F_OFF(F_EXPANDED_DISTLISTS,ps) && entry_is_listent(as.top_ent+as.cur_row)) cmd = MC_UNEXPAND; else if(F_OFF(F_CMBND_ABOOK_DISP,ps_global)) cmd = MC_POPUP; else cmd = MC_NONE; if(cmd == MC_NONE) clrbitn(TWO_KEY, bitmap); else{ menu_init_binding(km, '<', cmd, "<", cmd == MC_POPUP ? N_("AddressBkList") /* TRANSLATORS: Unexpand is the opposite of Expand. We might expand an address book distribution list to see all the members of the list. */ : N_("Unexpand"), TWO_KEY); menu_add_binding(km, ',', cmd); if(F_ON(F_ARROW_NAV,ps)) menu_add_binding(km, KEY_LEFT, cmd); } } else if(as.checkboxes && (as.n_addrbk > 1 || as.n_serv)){ if(checkedn){ if(entry_is_clickable_title(as.top_ent+as.cur_row)){ menu_init_binding(km, 'S', MC_CHOICE, "S", /* TRANSLATORS: Select something, choose something */ N_("Select"), TWO_KEY); } else{ menu_init_binding(km, 'S', MC_CHOICE, "S", "[" N_("Select") "]", TWO_KEY); def_key = TWO_KEY; } } else menu_init_binding(km, 'S', MC_CHOICE, "S", N_("Select"), TWO_KEY); } else clrbitn(TWO_KEY, bitmap); /* * The THREE_KEY is used as the select key in selection mode, * but it doesn't show up at the top-level. Instead, the * key becomes the ViewAbook key. */ if(entry_is_askserver(as.top_ent+as.cur_row) && !as.checkboxes){ menu_init_binding(km, '>', MC_QUERY_SERV, ">", "[" N_("Search") "]", THREE_KEY); menu_add_binding(km, 's', MC_QUERY_SERV); menu_add_binding(km, '.', MC_QUERY_SERV); if(F_ON(F_ARROW_NAV,ps)) menu_add_binding(km, KEY_RIGHT, MC_QUERY_SERV); } else if(entry_is_clickable_title(as.top_ent+as.cur_row)){ /* TRANSLATORS: View this address book */ menu_init_binding(km, '>', MC_OPENABOOK, ">", "[" N_("ViewAbook") "]", THREE_KEY); menu_add_binding(km, 'v', MC_OPENABOOK); menu_add_binding(km, '.', MC_OPENABOOK); if(F_ON(F_ARROW_NAV,ps)) menu_add_binding(km, KEY_RIGHT, MC_OPENABOOK); } else if(cur_is_open()){ menu_init_binding(km, 'S', MC_CHOICE, "S", "[" N_("Select") "]", THREE_KEY); } else clrbitn(THREE_KEY, bitmap); KS_OSDATASET(&km->keys[THREE_KEY], KS_NONE); /* * The Expand command gets stuck out in right field. */ if(entry_is_clickable(as.top_ent+as.cur_row) && !entry_is_clickable_title(as.top_ent+as.cur_row)){ menu_init_binding(km, '>', MC_EXPAND, ">", N_("Expand"), SENDTO_KEY); menu_add_binding(km, '.', MC_EXPAND); if(F_ON(F_ARROW_NAV,ps)) menu_add_binding(km, KEY_RIGHT, MC_EXPAND); } else clrbitn(SENDTO_KEY, bitmap); if(cur_is_open() && as.checkboxes){ /* TRANSLATORS: Set/Unset means that this particular command will toggle between setting something (turning it on) and unsetting it (turning it off). For example, it might be a program option that can be turned on or off or it might be a way to mark which addresses to send a message to. */ menu_init_binding(km, 'X', MC_TOGGLE, "X", N_("Set/Unset"), DELETE_KEY); } else if(cur_is_open() && listmode_ok){ /* TRANSLATORS: List mode is a type of screen in pine that allows the user to select several of something. This is the name of the command to go into the List mode style of operating. */ menu_init_binding(km, 'L', MC_LISTMODE, "L", N_("ListMode"), DELETE_KEY); } else clrbitn(DELETE_KEY, bitmap); if(cur_is_open() && as.checkboxes){ menu_init_binding(km, 'A', MC_SELALL, "A", /* TRANSLATORS: when selecting from a list of items the unsetall (unset all) means to start over with nothing selected. The set all command means select everything in the list. */ checkedn ? N_("unsetAll") : N_("setAll"), ADD_KEY); } else clrbitn(ADD_KEY, bitmap); KS_OSDATASET(&km->keys[DELETE_KEY], KS_NONE); } else{ /* * Reset first Other key. Selection screen may have * blasted it. Do this by hand because menu_init_binding * will remove the other two OTHER CMDS bindings. * Should figure out how to do this correctly with a * reasonable function call. */ km->keys[OTHER_KEY].name = "O"; /* TRANSLATORS: This is the name of the command that will show which other commands are available. 12 commands are shown at the bottom of the screen, this command would show the next set of 12 */ km->keys[OTHER_KEY].label = N_("OTHER CMDS"); km->keys[OTHER_KEY].bind.cmd = MC_OTHER; km->keys[OTHER_KEY].bind.ch[0] = 'O'; km->keys[OTHER_KEY].bind.nch = 1; KS_OSDATASET(&km->keys[OTHER_KEY], KS_NONE); km->how_many = 2; if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ if(F_ON(F_ENABLE_AGG_OPS, ps)) km->how_many = 3; /* * The TWO_KEY is used as the go back key in * non-selection mode. */ if(F_OFF(F_EXPANDED_DISTLISTS,ps) && entry_is_listent(as.top_ent+as.cur_row)){ cmd = MC_UNEXPAND; menu_init_binding(km, '<', cmd, "<", N_("Unexpand"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE); } else{ cmd = MC_MAIN; menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU); } if(cur_is_open()){ /* * Add or delete entries from this address book. */ /* TRANSLATORS: Add a new entry (this is a command) */ menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"), ADD_KEY); menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"), DELETE_KEY); /* TRANSLATORS: Compose a message to be sent to the current address book entry */ menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"), SENDTO_KEY); KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER); menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"), RCOMPOSE_KEY); } else{ clrbitn(ADD_KEY, bitmap); clrbitn(DELETE_KEY, bitmap); clrbitn(SENDTO_KEY, bitmap); clrbitn(RCOMPOSE_KEY, bitmap); clrbitn(SAVE_KEY, bitmap); clrbitn(TAKE_KEY, bitmap); clrbitn(FORW_KEY, bitmap); } clrbitn(SECONDARY_MAIN_KEY, bitmap); } else if(cur_is_open()){ if(F_ON(F_ENABLE_AGG_OPS, ps)) km->how_many = 3; /* * The TWO_KEY is used as the go back key in * non-selection mode. */ if(F_OFF(F_EXPANDED_DISTLISTS,ps) && entry_is_listent(as.top_ent+as.cur_row)){ cmd = MC_UNEXPAND; menu_init_binding(km, '<', cmd, "<", N_("Unexpand"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE); } else{ if(as.n_addrbk > 1 || as.n_serv){ cmd = MC_POPUP; menu_init_binding(km, '<', cmd, "<", N_("AddressBkList"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE); } else{ cmd = MC_MAIN; menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU); } } if(pab->access != NoAccess){ /* * Add or delete entries from this address book. */ menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"), ADD_KEY); menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"), DELETE_KEY); } else{ clrbitn(ADD_KEY, bitmap); clrbitn(DELETE_KEY, bitmap); } /* Find someplace to put Main Menu command */ if(cmd == MC_POPUP){ menu_init_binding(km, 'M', MC_MAIN, "M", N_("Main Menu"), SECONDARY_MAIN_KEY); KS_OSDATASET(&km->keys[SECONDARY_MAIN_KEY],KS_MAINMENU); } else clrbitn(SECONDARY_MAIN_KEY, bitmap); menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"), SENDTO_KEY); KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER); menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"), RCOMPOSE_KEY); } else{ /* * The TWO_KEY is used as the go back key in * non-selection mode. */ cmd = MC_MAIN; menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"), TWO_KEY); KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU); clrbitn(SENDTO_KEY, bitmap); clrbitn(RCOMPOSE_KEY, bitmap); clrbitn(ADD_KEY, bitmap); clrbitn(DELETE_KEY, bitmap); clrbitn(SECONDARY_MAIN_KEY, bitmap); clrbitn(SAVE_KEY, bitmap); clrbitn(TAKE_KEY, bitmap); clrbitn(FORW_KEY, bitmap); } /* can't be on third menu if we just reduced to 2 */ if(km->how_many == 2 && km->which == 2) km->which = 0; menu_add_binding(km, '<', cmd); menu_add_binding(km, ',', cmd); if(F_ON(F_ARROW_NAV,ps)) menu_add_binding(km, KEY_LEFT, cmd); KS_OSDATASET(&km->keys[DELETE_KEY], KS_DELETE); /* * The THREE_KEY is the burrow into the hierarchy key. */ if(entry_is_askserver(as.top_ent+as.cur_row)) cmd = MC_QUERY_SERV; else if(entry_is_clickable(as.top_ent+as.cur_row)){ if(entry_is_clickable_title(as.top_ent+as.cur_row)) cmd = MC_OPENABOOK; else cmd = MC_EXPAND; } else cmd = MC_VIEW_ENTRY; menu_init_binding(km, '>', cmd, ">", cmd == MC_EXPAND ? "[" N_("Expand") "]" : cmd == MC_QUERY_SERV ? "[" N_("Search") "]" : /* TRANSLATORS: In the address book the user can view a particular address book entry. It is called View/Update because the way to update an entry is to first view it and then there will be an opportunity to update it from there. */ cur_is_open() ? "[" N_("View/Update") "]" : "[" N_("ViewAbook") "]", THREE_KEY); if(cmd == MC_QUERY_SERV) menu_add_binding(km, 's', cmd); else if(cmd == MC_OPENABOOK || cmd == MC_VIEW_ENTRY) menu_add_binding(km, 'v', cmd); menu_add_binding(km, '.', cmd); if(F_ON(F_ARROW_NAV,ps)) menu_add_binding(km, KEY_RIGHT, cmd); } menu_add_binding(km, ctrl('M'), km->keys[def_key].bind.cmd); menu_add_binding(km, ctrl('J'), km->keys[def_key].bind.cmd); if(km_popped){ FOOTER_ROWS(ps) = 3; clearfooter(ps); } draw_keymenu(km, bitmap, ps->ttyo->screen_cols, 1-FOOTER_ROWS(ps), 0, what); ps->mangled_footer = 0; what = SameMenu; if(km_popped){ FOOTER_ROWS(ps) = 1; mark_keymenu_dirty(); } } rdonly = (pab && pab->access == ReadOnly); empty = is_empty(as.cur_row+as.top_ent); /*------------ display any status messages ------------------*/ if(km_popped){ FOOTER_ROWS(ps) = 3; mark_status_unknown(); } display_message(c); if(km_popped){ FOOTER_ROWS(ps) = 1; mark_status_unknown(); } if(F_OFF(F_SHOW_CURSOR, ps)){ /* reset each time through to catch screen size changes */ cursor_pos.row = ps->ttyo->screen_rows-FOOTER_ROWS(ps); cursor_pos.col = 0; } MoveCursor(cursor_pos.row, cursor_pos.col); /*---------------- Get command and validate -------------------*/ #ifdef MOUSE mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); register_mfunc(mouse_in_content, HEADER_ROWS(ps), 0, ps->ttyo->screen_rows-(FOOTER_ROWS(ps)+1), ps->ttyo->screen_cols); #endif /* sort out what help needs to be displayed if asked for */ if(as.config) gAbookHelp = h_abook_config; else if(are_selecting){ if(cur_is_open()){ if(style == SelectNickTake) gAbookHelp = h_abook_select_nicks_take; else if(selecting_one_nick) gAbookHelp = h_abook_select_nick; else if(as.checkboxes) gAbookHelp = h_abook_select_checks; else if(listmode_ok) gAbookHelp = h_abook_select_listmode; else gAbookHelp = h_abook_select_addr; } else gAbookHelp = h_abook_select_top; } else gAbookHelp = cur_is_open() ? h_abook_opened : h_abook_top; #ifdef _WINDOWS mswin_setscrollcallback(addr_scroll_callback); mswin_sethelptextcallback(pcpine_help_addrbook); #endif c = READ_COMMAND(&utf8str); #ifdef MOUSE clear_mfunc(mouse_in_content); #endif #ifdef _WINDOWS mswin_setscrollcallback(NULL); mswin_sethelptextcallback(NULL); #endif cmd = menu_command(c, km); dprint((2, "Addrbook command: %d (0x%x %s)\n", cmd, c, pretty_command(c))); /* this may be a safe place to update addrbooks */ if(ps->remote_abook_validity > 0 && adrbk_check_and_fix_all(ab_nesting_level < 2 && !any_ab_open() && checkedn == 0, 0, 0)) ps->mangled_footer = 1; if(km_popped) switch(cmd){ case MC_NONE: case MC_OTHER: case MC_RESIZE: case MC_REPAINT: km_popped++; break; default: clearfooter(ps); break; } /*------------- execute command ----------------*/ switch(cmd){ /*------------ Noop (new mail check) --------------*/ case MC_NONE: break; /*----------- Help -------------------*/ case MC_HELP: if(FOOTER_ROWS(ps) == 1 && km_popped == 0){ km_popped = 2; ps->mangled_footer = 1; break; } if(as.config) helper(gAbookHelp, _("HELP ON CONFIGURING ADDRESS BOOKS"), HLPD_NONE); else if(are_selecting) helper(gAbookHelp, _("HELP ON ADDRESS BOOK"), HLPD_SIMPLE | HLPD_NEWWIN); else /* general maintenance screen */ helper(gAbookHelp, _("HELP ON ADDRESS BOOK"), HLPD_NONE); /* * Helper() may have a Main Menu key. If user types that * they'll set next_screen. We don't have to do anything * special but we want to make sure that that doesn't happen * when we're selecting, even though it shouldn't be * possible because HLPD_SIMPLE is set. */ if(are_selecting) ps->next_screen = SCREEN_FUN_NULL; /* probably not needed */ ps->mangled_screen = 1; break; /*---------- display other key bindings ------*/ case MC_OTHER: warn_other_cmds(); what = NextMenu; ps->mangled_footer = 1; break; /*------------ Unexpand list -----------------*/ case MC_UNEXPAND: if(F_OFF(F_EXPANDED_DISTLISTS,ps) && entry_is_listent(as.top_ent+as.cur_row)){ DL_CACHE_S *dlc_to_flush; long global_row_num; /* * unexpand list */ /* * redraw screen starting with first ListEnt */ dl = dlist(as.top_ent+as.cur_row); dlc_to_flush = get_dlc(as.top_ent+as.cur_row); /* * New cur_row should be the line after the ListHead line * that takes the place of the first address in the list. */ as.cur_row = as.cur_row - dl->l_offset; /* * If the list header for the new row is off the screen, * adjust it. */ if(as.cur_row < 0){ /* center it */ global_row_num = dlc_to_flush->global_row - dlc_to_flush->dlcoffset; as.top_ent = first_line(global_row_num - as.l_p_page/2L); as.cur_row = global_row_num - as.top_ent; start_disp = 0; } else if(as.cur_row == 0){ /* just slide up in this case */ as.top_ent -= 1; as.cur_row = 1; start_disp = 0; } else start_disp = as.cur_row; exp_unset_expanded(pab->address_book->exp, (a_c_arg_t)dlc_to_flush->dlcelnum); flush_dlc_from_cache(dlc_to_flush); ps->mangled_body = 1; ps->mangled_footer = 1; } else q_status_message(SM_ORDER | SM_DING, 3, 4, "Can't happen in MC_UNEXPAND"); break; /*------ Popup to top level display ----------*/ case MC_POPUP: if(F_ON(F_EXPANDED_DISTLISTS,ps) || !entry_is_listent(as.top_ent+as.cur_row)){ DL_CACHE_S dlc_restart; long new_row; (void)init_addrbooks((checkedn || as.selections) ? ThreeQuartOpen : HalfOpen, 0, 0, !are_selecting); dlc_restart.adrbk_num = as.cur; dlc_restart.type = DlcTitle; warp_to_dlc(&dlc_restart, 0L); /* * Put the current entry in a nice spot on the screen. * Will everything above fit and still leave ours on screen? */ new_row = LINES_PER_ABOOK * as.cur + (((pab->type & GLOBAL) && (as.how_many_personals > 0 || as.config)) ? XTRA_LINES_BETWEEN : 0) + ((as.how_many_personals == 0 && as.config) ? LINES_PER_ADD_LINE : 0); new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0); as.cur_row = new_row; as.top_ent = 0L - as.cur_row; start_disp = 0; ps->mangled_screen = 1; } else q_status_message(SM_ORDER | SM_DING, 3, 4, "Can't happen in MC_POPUP"); break; /*------------- Back to main menu or exit to caller -------*/ case MC_EXIT: case MC_MAIN: if(!are_selecting) ps->next_screen = main_menu_screen; if(!(are_selecting && as.checkboxes && checkedn > 0) /* TRANSLATORS: we are asking for confirmation about abandonding selections in the address book. */ || want_to(_("Really abandon your selections "), 'y', 'x', NO_HELP, WT_NORM) == 'y') quit++; else ps->next_screen = SCREEN_FUN_NULL; break; /*------- Open an address book ----------*/ case MC_OPENABOOK: openabook: if(entry_is_clickable_title(as.top_ent+as.cur_row)){ DL_CACHE_S *dlc_to_flush; dlc_to_flush = get_dlc(as.top_ent+as.cur_row); if(dlc_to_flush->type == DlcTitle || dlc_to_flush->type == DlcClickHereCmb){ /* * open this addrbook and fill in display list */ init_abook(pab, Open); if(pab->access == ReadWrite || pab->access == ReadOnly){ if(!are_selecting && pab->access == ReadOnly) q_status_message1(SM_ORDER, 0, 4, _("AddressBook %s is Read Only"), pab->abnick); /* * successful open, burrow into addrbook */ if(F_OFF(F_CMBND_ABOOK_DISP,ps_global)){ warp_to_top_of_abook(as.cur); as.top_ent = 0L; as.cur_row = 0L; start_disp = 0; ps->mangled_screen = 1; } else{ flush_dlc_from_cache(dlc_to_flush); start_disp = as.cur_row; ps->mangled_footer = 1; ps->mangled_body = 1; } } else{ /* open failed */ /* * Flush the title line so that it will change into * a permission denied line, */ flush_dlc_from_cache(dlc_to_flush); start_disp = as.cur_row; ps->mangled_footer = 1; ps->mangled_body = 1; } } else if(dlc_to_flush->type == DlcTitleNoPerm) q_status_message(SM_ORDER, 0, 4, _("Cannot access address book.")); } else q_status_message(SM_ORDER | SM_DING, 3, 4, "Can't happen in MC_OPENABOOK"); break; /*------- Expand addresses in List ------*/ case MC_EXPAND: expand: if(entry_is_clickable(as.top_ent+as.cur_row)){ DL_CACHE_S *dlc_to_flush; dlc_to_flush = get_dlc(as.top_ent+as.cur_row); if(dlc_to_flush->type == DlcListClickHere){ start_disp = as.cur_row; /* will redraw from here down */ /* * Mark this list expanded, then flush the * current line from dlc cache. When we get the * line again it will notice the expanded flag and change * the type to DlcListEnt (if any entries). */ if(F_OFF(F_EXPANDED_DISTLISTS,ps)) exp_set_expanded(pab->address_book->exp, (a_c_arg_t)dlc_to_flush->dlcelnum); flush_dlc_from_cache(dlc_to_flush); dlc_to_flush = get_dlc(as.top_ent+as.cur_row); /* * If list is empty, back cursor up one. */ if(dlc_to_flush->type == DlcListEmpty){ as.cur_row--; start_disp--; if(as.cur_row < 0){ as.top_ent--; as.cur_row = 0; start_disp = 0; } } ps->mangled_body = 1; } } else q_status_message(SM_ORDER | SM_DING, 3, 4, "Can't happen in MC_EXPAND"); break; /*------- Select ---------------*/ case MC_CHOICE: select: if(are_selecting){ /* Select an entry to mail to or a nickname to add to */ if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER | SM_DING, 0, 4, _("No entries in address book. Use ExitSelect to leave address books")); break; } if(as.checkboxes || is_addr(as.top_ent+as.cur_row)){ BuildTo bldto; char *to = NULL; char *error = NULL; AdrBk_Entry *abe; dl = dlist(as.top_ent+as.cur_row); if(selecting_one_nick){ char nickbuf[MAX_NICKNAME + 1]; strncpy(nickbuf, ae(as.top_ent+as.cur_row)->nickname, sizeof(nickbuf)-1); nickbuf[sizeof(nickbuf)-1] = '\0'; return(cpystr(nickbuf)); } else if(as.checkboxes && checkedn <= 0){ q_status_message(SM_ORDER, 0, 1, _("Use \"X\" to mark addresses or lists")); break; } else if(as.checkboxes){ size_t incr = 100, avail, alloced; /* * Have to run through all of the checked entries * in all of the address books. * Put the nicknames together into one long * string with comma separators and let * our_build_address handle the parsing. */ to = (char *)fs_get(incr); *to = '\0'; avail = incr; alloced = incr; for(i = 0; i < as.n_addrbk; i++){ EXPANDED_S *next_one; adrbk_cntr_t num; AddrScrn_Disp fake_dl; char *a_string; pab = &as.adrbks[i]; if(pab->address_book) next_one = pab->address_book->checks; else continue; while((num = entry_get_next(&next_one)) != NO_NEXT){ abe = adrbk_get_ae(pab->address_book, (a_c_arg_t) num); /* * Since we're picking up address book entries * directly from the address books and have * no knowledge of the display lines they came * from, we don't know the dl's that go with * them. We need to pass a dl to abe_to_nick * but it really is only going to use the * type in this case. */ dl = &fake_dl; dl->type = (abe->tag == Single) ? Simple : ListHead; a_string = abe_to_nick_or_addr_string(abe, dl, i); while(abe && avail < (size_t)strlen(a_string)+1){ alloced += incr; avail += incr; fs_resize((void **)&to, alloced); } if(!*to){ strncpy(to, a_string, alloced); to[alloced-1] = '\0'; } else{ strncat(to, ",", alloced-strlen(to)-1); strncat(to, a_string, alloced-strlen(to)-1); } avail -= (strlen(a_string) + 1); } } /* * Return the nickname list for lcc so that the * correct fullname can make it to the To line. * If we expand it ahead of time, the list name * and first user's fullname will get mushed together. * If an entry doesn't have a nickname then we're * out of luck as far as getting the right entry * in the To line goes. */ if(selecting_mult_nicks) return(to); bldto.type = Str; bldto.arg.str = to; } else{ /* Select an address, but not using checkboxes */ if(selecting_mult_nicks){ if(dl->type != ListHead && style == SelectAddrLccCom){ q_status_message(SM_ORDER, 0, 4, _("You may only select lists for Lcc, use Bcc for other addresses")); break; } else{ /* * Even though we're supposedly selecting * nicknames, we have a special case here to * select a single member of a distribution * list. This happens with style SelectNicksCom * which is the regular ^T entry from the * composer, and it allows somebody to mail to * a single member of a distribution list. */ abe = ae(as.top_ent+as.cur_row); return(abe_to_nick_or_addr_string(abe, dl, as.cur)); } } else{ if(dl->type == ListEnt){ bldto.type = Str; bldto.arg.str = listmem_from_dl(pab->address_book, dl); } else{ bldto.type = Abe; bldto.arg.abe = ae(as.top_ent+as.cur_row); } } } (void)our_build_address(bldto, &addr, &error, NULL, save_and_restore); /* Have to rfc1522_decode the addr */ if(addr){ char *tmp_a_string, *p; ADDRESS *a = NULL; if(style == SelectAddrNoFull){ tmp_a_string = cpystr(addr); rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain); fs_give((void **)&tmp_a_string); if(a){ fs_give((void **)&addr); addr = cpystr(simple_addr_string(a, tmp_20k_buf, SIZEOF_20KBUF)); mail_free_address(&a); } } else if(style == SelectMultNoFull){ tmp_a_string = cpystr(addr); rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain); fs_give((void **)&tmp_a_string); if(a){ fs_give((void **)&addr); addr = cpystr(simple_mult_addr_string(a, tmp_20k_buf, SIZEOF_20KBUF, ",")); mail_free_address(&a); } } else{ size_t len; len = 4*strlen(addr)+1; p = (char *)fs_get(len * sizeof(char)); if(rfc1522_decode_to_utf8((unsigned char *)p, len, addr) == (unsigned char *)p){ fs_give((void **)&addr); addr = p; } else fs_give((void **)&p); } } if(to) fs_give((void **)&to); if(error){ q_status_message1(SM_ORDER, 3, 4, "%s", error); fs_give((void **)&error); } return(addr); /* Caller frees this */ } else{ if(entry_is_clickable(as.top_ent+as.cur_row)) clickable_warning(as.top_ent+as.cur_row); else if(entry_is_askserver(as.top_ent+as.cur_row)) q_status_message(SM_ORDER, 3, 4, _("Use select to select an address or addresses from address books")); else q_status_message(SM_ORDER, 3, 4, _("No address selected")); break; } } else q_status_message(SM_ORDER | SM_DING, 3, 4, "Can't happen in MC_CHOICE"); break; /*----- View an entry --------------------*/ case MC_VIEW_ENTRY: view: if(empty){ empty_warning(as.top_ent+as.cur_row); break; } view_abook_entry(ps, as.top_ent+as.cur_row); break; /*----- Add new ---------*/ case MC_ADD: {long old_l_p_p, old_top_ent, old_cur_row; if(adrbk_check_all_validity_now()){ if(resync_screen(pab, style, checkedn)){ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Address book changed. AddNew cancelled. Try again.")); ps->mangled_screen = 1; break; } } if(rdonly){ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only")); break; } warped = 0; dprint((9, "Calling edit_entry to add entry manually\n")); /* TRANSLATORS: add as in add a new entry to something */ edit_entry(pab->address_book, (AdrBk_Entry *)NULL, NO_NEXT, NotSet, 0, &warped, _("add")); /* * Warped means we got plopped down somewhere in the display * list so that we don't know where we are relative to where * we were before we warped. The current line number will * be zero, since that is what the warp would have set. */ if(warped){ as.top_ent = first_line(0L - as.l_p_page/2L); as.cur_row = 0L - as.top_ent; } else{ /* * If we didn't warp, that means we didn't change at all, * so keep old screen. */ old_l_p_p = as.l_p_page; old_top_ent = as.top_ent; old_cur_row = as.cur_row; } /* Window size may have changed while in pico. */ ab_resize(); /* fix up what ab_resize messed up */ if(!warped && old_l_p_p == as.l_p_page){ as.top_ent = old_top_ent; as.cur_row = old_cur_row; as.old_cur_row = old_cur_row; } } ps->mangled_screen = 1; break; /*---------- Add a new address book -------------------*/ case MC_ADDABOOK: {int old_abook_num, new_abook_num, new_row, global; int stay_put, old_cur_row; add_abook: stay_put = (dlist(as.top_ent+as.cur_row)->type == AddFirstPers || dlist(as.top_ent+as.cur_row)->type == AddFirstGlob); old_cur_row = as.cur_row; old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row); global = (dlist(as.top_ent+as.cur_row)->type == AddFirstGlob || (dlist(as.top_ent+as.cur_row)->type != AddFirstPers && (old_abook_num >= as.how_many_personals) && as.n_addrbk > 0)); if((new_abook_num = ab_add_abook(global, stay_put ? -1 : adrbk_num_from_lineno(as.top_ent+as.cur_row))) >= 0){ DL_CACHE_S dlc_restart; (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting); erase_checks(); erase_selections(); dlc_restart.adrbk_num = new_abook_num; dlc_restart.type = DlcTitle; warp_to_dlc(&dlc_restart, 0L); /* * Put the current entry in a nice spot on the screen. */ new_row = old_cur_row + (stay_put ? 0 : LINES_PER_ABOOK*(new_abook_num - old_abook_num)); if(new_row >= 0 && new_row <= (as.l_p_page-VIS_LINES_PER_ABOOK))/* ok, use it */ as.cur_row = new_row; else{ /* * Will everything above fit and still leave ours on screen? */ new_row = LINES_PER_ABOOK * new_abook_num + ((global && (as.how_many_personals > 0 || as.config)) ? XTRA_LINES_BETWEEN : 0) + ((as.how_many_personals == 0 && as.config) ? LINES_PER_ADD_LINE : 0); new_row = MAX(MIN(new_row, as.l_p_page - VIS_LINES_PER_ABOOK), 0); as.cur_row = new_row; } as.top_ent = 0L - as.cur_row; dprint((5, "addrbook added: %s\n", as.adrbks[new_abook_num].filename ? as.adrbks[new_abook_num].filename : "?")); } ps->mangled_screen = 1; } break; /*---------- Change address book config -------------------*/ case MC_EDITABOOK: change_abook: if(entry_is_clickable_title(as.top_ent+as.cur_row)){ int abook_num, old_cur_row, global; long old_top_ent; DL_CACHE_S dlc_restart, *dlc; char *serv = NULL, *folder = NULL, *p, *q; char *nick = NULL, *file = NULL; abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row); global = (abook_num >= as.how_many_personals); old_cur_row = as.cur_row; old_top_ent = as.top_ent; dlc = get_dlc(as.top_ent+as.cur_row); dlc_restart = *dlc; if(global) q = ps_global->VAR_GLOB_ADDRBOOK[abook_num - as.how_many_personals]; else q = ps_global->VAR_ADDRESSBOOK[abook_num]; get_pair(q, &nick, &file, 0, 0); if(nick && !*nick) fs_give((void **)&nick); if(file && *file == '{'){ q = file + 1; if((p = strindex(file, '}'))){ *p = '\0'; serv = q; folder = p+1; } else{ q_status_message1(SM_ORDER|SM_DING, 0, 4, _("Missing \"}\" in config: %s"), q); if(nick) fs_give((void **)&nick); if(file) fs_give((void **)&file); break; } } else folder = file; if(ab_edit_abook(global, abook_num, serv, folder, nick) >= 0){ (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting); erase_checks(); erase_selections(); dlc_restart.type = DlcTitle; warp_to_dlc(&dlc_restart, old_top_ent+(long)old_cur_row); as.cur_row = old_cur_row; as.top_ent = old_top_ent; dprint((5, "addrbook config edited: %s\n", as.adrbks[dlc_restart.adrbk_num].filename ? as.adrbks[dlc_restart.adrbk_num].filename : "?")); } if(nick) fs_give((void **)&nick); if(file) fs_give((void **)&file); ps->mangled_screen = 1; } else /* TRANSLATORS: the user tried to change the current line of the address book but the line could not be changed */ q_status_message(SM_ORDER, 0, 4, _("Not a changeable line")); break; /*---------- Delete an address book -------------------*/ case MC_DELABOOK: if(as.n_addrbk == 0){ q_status_message(SM_ORDER, 0, 4, _("Nothing to delete")); break; } {char *err = NULL; if(ab_del_abook(as.top_ent+as.cur_row, command_line, &err) >= 0){ DL_CACHE_S dlc_restart; int new_abook_num, old_abook_num, old_pers, old_glob, new_row; /* * Careful, these are only ok because ab_del_abook didn't * mess with the as globals, like as.how_many_personals. * The addrbook_reset does reset them, of course. */ old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row); old_pers = as.how_many_personals; old_glob = as.n_addrbk - as.how_many_personals; addrbook_reset(); (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting); erase_checks(); erase_selections(); if(old_abook_num >= as.n_addrbk) /* we deleted last addrbook */ new_abook_num = as.n_addrbk - 1; else new_abook_num = old_abook_num; /* * Pick a line to highlight and center */ if(as.how_many_personals == 0 && old_pers == 1) dlc_restart.type = DlcPersAdd; else if((as.n_addrbk - as.how_many_personals) == 0 && old_glob == 1) dlc_restart.type = DlcGlobAdd; else if(as.n_addrbk == 0) dlc_restart.type = DlcPersAdd; else{ dlc_restart.adrbk_num = new_abook_num; dlc_restart.type = DlcTitle; } warp_to_dlc(&dlc_restart, 0L); /* * Will everything above fit and still leave ours on screen? */ if(dlc_restart.type == DlcTitle) new_row = LINES_PER_ABOOK * new_abook_num + (((as.adrbks[new_abook_num].type & GLOBAL) && (as.how_many_personals > 0 || as.config)) ? XTRA_LINES_BETWEEN : 0) + ((as.how_many_personals == 0 && as.config) ? LINES_PER_ADD_LINE : 0); else if(dlc_restart.type == DlcGlobAdd) new_row = LINES_PER_ABOOK * as.n_addrbk + ((as.how_many_personals > 0 || as.config) ? XTRA_LINES_BETWEEN : 0) + ((as.how_many_personals == 0 && as.config) ? LINES_PER_ADD_LINE : 0); else new_row = 0; new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0); as.cur_row = new_row; as.top_ent = 0L - as.cur_row; start_disp = 0; ps->mangled_body = 1; ps->mangled_footer = 1; /* TRANSLATORS: This is just comforting confirmation that the address book being deleted was successfully deleted */ q_status_message(SM_ORDER, 0, 3, _("Address book deleted")); } else{ if(err){ q_status_message(SM_ORDER, 0, 4, err); dprint((5, "addrbook delete failed: %s\n", err ? err : "?")); } } } break; /*---- Reorder an addressbook list ---------*/ case MC_SHUFFLE: if(entry_is_addkey(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("Highlight entry you wish to shuffle")); break; } {int slide; char *msg = NULL; int ret, new_anum; if((ret = ab_shuffle(pab, &slide, command_line, &msg)) > 0){ DL_CACHE_S dlc_restart; int new_row; new_anum = ret - 1; /* see ab_shuffle return value */ addrbook_reset(); (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting); erase_checks(); erase_selections(); /* put cursor on new_anum */ dlc_restart.adrbk_num = new_anum; dlc_restart.type = DlcTitle; warp_to_dlc(&dlc_restart, 0L); /* * Will everything above fit and still leave ours on screen? */ new_row = LINES_PER_ABOOK * new_anum + (((as.adrbks[new_anum].type & GLOBAL) && (as.how_many_personals > 0 || as.config)) ? XTRA_LINES_BETWEEN : 0) + ((as.how_many_personals == 0 && as.config) ? LINES_PER_ADD_LINE : 0); new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0); as.cur_row = new_row; as.top_ent = 0L - as.cur_row; start_disp = 0; ps->mangled_body = 1; } else if(ret == 0){ DL_CACHE_S *dlc_to_flush; if(slide < 0){ /* moved it up */ as.cur_row += slide; start_disp = MAX(as.cur_row - 1, 0); dlc_to_flush = get_dlc(as.top_ent+as.cur_row); /* * If we backed off the top of the screen, just * inch the display up so we can see it. */ if(as.cur_row < 0){ as.top_ent += as.cur_row; /* cur_row is negative */ as.cur_row = 0; start_disp = 0; } } else{ /* moved it down */ start_disp = as.cur_row; dlc_to_flush = get_dlc(as.top_ent+as.cur_row); as.cur_row += slide; if(as.cur_row > as.l_p_page - VIS_LINES_PER_ABOOK){ as.top_ent += (as.cur_row - (as.l_p_page - VIS_LINES_PER_ABOOK)); as.cur_row = MAX(as.l_p_page-VIS_LINES_PER_ABOOK, 0); start_disp = 0; } } flush_dlc_from_cache(dlc_to_flush); ps->mangled_body = 1; } q_status_message(SM_ORDER, 0, 3, msg ? msg : (ret < 0) ? _("Shuffle failed") : _("Address books shuffled")); if(ret < 0) dprint((5, "addrbook shuffle failed: %s\n", msg ? msg : "?")); if(msg) fs_give((void **)&msg); } break; /*----------------------- Move Up ---------------------*/ case MC_CHARUP: case MC_PREVITEM: r = prev_selectable_line(as.cur_row+as.top_ent, &new_line); if(r == 0){ /* find first line so everything is displayed */ new_top_ent = as.cur_row+as.top_ent; for(dl=dlist(new_top_ent-1); dl->type != Beginning; dl = dlist((--new_top_ent) - 1)) ; if(new_top_ent == as.top_ent || (as.cur_row + (as.top_ent-new_top_ent) > as.l_p_page - 1)){ q_status_message(SM_INFO, 0, 1, _("Already on first line.")); } else{ as.cur_row += (as.top_ent - new_top_ent); as.top_ent = new_top_ent; start_disp = 0; ps->mangled_body = 1; } break; } i = ((cmd == MC_CHARUP) && dlist(as.top_ent - 1L)->type != Beginning) ? MIN(HS_MARGIN(ps), as.cur_row) : 0; as.cur_row = new_line - as.top_ent; if(as.cur_row - i < 0){ if(cmd == MC_CHARUP){ /*-- Past top of page --*/ as.top_ent += (as.cur_row - i); as.cur_row = i; } else{ new_top_ent = first_line(as.top_ent - as.l_p_page); as.cur_row += (as.top_ent - new_top_ent); as.top_ent = new_top_ent; /* if it is still off screen */ if(as.cur_row - i < 0){ as.top_ent += (as.cur_row - i); as.cur_row = i; } } start_disp = 0; ps->mangled_body = 1; } else current_changed_flag++; break; /*------------------- Move Down -------------------*/ case MC_CHARDOWN: case MC_NEXTITEM: r = next_selectable_line(as.cur_row+as.top_ent, &new_line); if(r == 0){ long new_end_line; /* find last line so everything is displayed */ new_end_line = as.cur_row+as.top_ent; for(dl=dlist(new_end_line+1); dl->type != End; dl = dlist((++new_end_line)+1)) ; if(new_end_line - as.top_ent <= as.l_p_page - 1 || as.cur_row - (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){ q_status_message(SM_INFO, 0, 1, _("Already on last line.")); } else{ as.cur_row -= (new_end_line-as.top_ent-(as.l_p_page-1)); as.top_ent += (new_end_line-as.top_ent-(as.l_p_page-1)); start_disp = 0; ps->mangled_body = 1; } break; } /* adjust for scrolling margin */ i = (cmd == MC_CHARDOWN) ? HS_MARGIN(ps) : 0; as.cur_row = new_line - as.top_ent; if(as.cur_row >= as.l_p_page - i){ if(cmd == MC_CHARDOWN){ /*-- Past bottom of page --*/ as.top_ent += (as.cur_row - (as.l_p_page - i) + 1); as.cur_row = (as.l_p_page - i) - 1; } else{ /*-- Changed pages --*/ as.top_ent += as.l_p_page; as.cur_row -= as.l_p_page; /* if it is still off screen */ if(as.cur_row >= as.l_p_page - i){ as.top_ent += (as.cur_row - (as.l_p_page - i) + 1); as.cur_row = (as.l_p_page - i) - 1; } } start_disp = 0; ps->mangled_body = 1; } else current_changed_flag++; break; #ifdef MOUSE case MC_MOUSE: { MOUSEPRESS mp; /* * Get the mouse down. Convert to content row number. * If the row is selectable, do the single or double click * operation. */ mouse_get_last(NULL, &mp); mp.row -= HEADER_ROWS(ps); if(line_is_selectable(as.top_ent + mp.row)){ if(mp.button == M_BUTTON_LEFT){ if(mp.doubleclick){ /* * A mouse double click does the default action to * the selected line. Since we need to do a goto * to get there, we have this ugly bit of code. * * On the first mouse click we go around the loop * once and set def_key appropriately for the * line as.top_ent+mp.row, which will then be * the same as as.top_ent+as.cur_row. */ switch(km->keys[def_key].bind.cmd){ case MC_CHOICE: if (as.checkboxes) goto togglex; else goto select; case MC_OPENABOOK: goto openabook; case MC_EXPAND: goto expand; case MC_TOGGLE: goto togglex; case MC_SELALL: goto selall; case MC_VIEW_ENTRY: goto view; case MC_ADDABOOK: goto add_abook; case MC_EDITABOOK: goto change_abook; #ifdef ENABLE_LDAP case MC_QUERY_SERV: goto q_server; #endif default: q_status_message(SM_INFO, 0, 1, "Can't happen in MC_MOUSE"); break; } } as.cur_row = mp.row; current_changed_flag++; } else if(mp.button == M_BUTTON_RIGHT){ #ifdef _WINDOWS int need_redraw; #endif as.cur_row = mp.row; current_changed_flag++; #ifdef _WINDOWS need_redraw = calculate_field_widths(); /*---------- Update the current entry, (move or change) -------*/ display_book(need_redraw ? 0 : as.cur_row, as.cur_row, as.old_cur_row, need_redraw, &cursor_pos); as.old_cur_row = as.cur_row; current_changed_flag = 0; as.cur = cur_addr_book(); pab = (as.n_addrbk && !entry_is_askserver(as.top_ent+as.cur_row)) ? &as.adrbks[as.cur] : NULL; if(!mp.doubleclick){ MPopup addr_pu[20]; int n, i; /* Make what they clicked "current" */ memset(addr_pu, 0, 20 * sizeof(MPopup)); addr_pu[n = 0].type = tQueue; addr_pu[n].data.val = km->keys[def_key].bind.ch[0]; addr_pu[n].label.style = lNormal; addr_pu[n++].label.string = km->keys[def_key].label; if((i = menu_clear_binding(km, '<')) != MC_UNKNOWN){ menu_add_binding(km, '<', i); if((i = menu_binding_index(km, i)) >= 0){ addr_pu[n++].type = tSeparator; addr_pu[n].type = tQueue; addr_pu[n].data.val = km->keys[i].bind.ch[0]; addr_pu[n].label.style = lNormal; addr_pu[n++].label.string = km->keys[i].label; } } addr_pu[n].type = tTail; mswin_popup(addr_pu); } #endif } } } break; #endif /*------------- Page Up or Down --------*/ case MC_PAGEUP: case MC_PAGEDN: if(cmd == MC_PAGEUP){ /* find first line on prev page */ new_top_ent = first_line(as.top_ent - as.l_p_page); if(new_top_ent == NO_LINE) break; /* find first selectable line */ fl = first_selectable_line(new_top_ent); /* If we didn't move, we'd better move now */ if(fl == as.top_ent+as.cur_row){ if(!prev_selectable_line(as.cur_row+as.top_ent, &new_line)){ long lineno; /* find first line so everything is displayed */ lineno = as.cur_row+as.top_ent; for(dl=dlist(lineno); dl->type != Beginning; dl = dlist(--lineno)) ; /* * If this new_top_ent is the same as the old_top_ent * we'll get the warning message. */ new_top_ent = first_line(lineno); if(fl - new_top_ent >= as.l_p_page) new_top_ent += (fl - new_top_ent - as.l_p_page + 1); } else fl = new_line; } if(fl == NO_LINE) break; if(as.top_ent == new_top_ent && as.cur_row == (fl-as.top_ent)){ q_status_message(SM_INFO, 0, 1, _("Already on first page.")); break; } if(as.top_ent == new_top_ent) current_changed_flag++; else as.top_ent = new_top_ent; } else{ /* Down */ /* find first selectable line on next page */ fl = first_selectable_line(as.top_ent + as.l_p_page); if(fl == NO_LINE) break; /* if there is another page, scroll */ if(fl - as.top_ent >= as.l_p_page){ new_top_ent = as.top_ent + as.l_p_page; } /* on last page already */ else{ new_top_ent = as.top_ent; if(as.cur_row == (fl - as.top_ent)){ /* no change */ long new_end_line; /* find last line so everything is displayed */ new_end_line = as.cur_row+as.top_ent; for(dl=dlist(new_end_line+1); dl->type != End; dl = dlist((++new_end_line)+1)) ; if(new_end_line - as.top_ent <= as.l_p_page - 1 || as.cur_row - (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){ q_status_message(SM_INFO, 0, 1, _("Already on last page.")); } else{ as.cur_row -= (new_end_line-as.top_ent-(as.l_p_page-1)); as.top_ent += (new_end_line-as.top_ent-(as.l_p_page-1)); start_disp = 0; ps->mangled_body = 1; } break; } } if(as.top_ent == new_top_ent) current_changed_flag++; else as.top_ent = new_top_ent; } /* * Stuff in common for up or down. */ as.cur_row = fl - as.top_ent; /* if it is still off screen */ if(as.cur_row < 0){ as.top_ent += as.cur_row; as.cur_row = 0; } else if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row - as.l_p_page + 1); as.cur_row = as.l_p_page - 1; } if(!current_changed_flag){ ps->mangled_body = 1; start_disp = 0; } break; /*------------- Delete item from addrbook ---------*/ case MC_DELETE: if(adrbk_check_all_validity_now()){ if(resync_screen(pab, style, checkedn)){ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Address book changed. Delete cancelled. Try again.")); ps->mangled_screen = 1; break; } } if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to delete")); break; } if(entry_is_clickable(as.top_ent+as.cur_row)){ clickable_warning(as.top_ent+as.cur_row); break; } if(rdonly){ q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only")); break; } if(empty){ empty_warning(as.top_ent+as.cur_row); break; } warped = 0; did_delete = single_entry_delete(pab->address_book, as.cur_row+as.top_ent, &warped); ps->mangled_footer = 1; if(did_delete){ if(warped){ as.top_ent = first_line(0L - as.l_p_page/2L); as.cur_row = 0L - as.top_ent; start_disp = 0; } else{ /* * In case the line we're now at is not a selectable * field. */ new_line = first_selectable_line(as.cur_row+as.top_ent); if(new_line != NO_LINE && new_line != as.cur_row+as.top_ent){ as.cur_row = new_line - as.top_ent; if(as.cur_row < 0){ as.top_ent -= as.l_p_page; as.cur_row += as.l_p_page; } else if(as.cur_row >= as.l_p_page){ as.top_ent += as.l_p_page; as.cur_row -= as.l_p_page; } } start_disp = MIN(as.cur_row, as.old_cur_row); } ps->mangled_body = 1; } break; /*------------- Toggle checkbox ---------*/ case MC_TOGGLE: togglex: if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to select")); break; } if(entry_is_clickable(as.top_ent+as.cur_row)){ clickable_warning(as.top_ent+as.cur_row); break; } if(empty){ empty_warning(as.top_ent+as.cur_row); break; } if(is_addr(as.top_ent+as.cur_row)){ dl = dlist(as.top_ent+as.cur_row); if(style == SelectAddrLccCom && dl->type == ListEnt) q_status_message(SM_ORDER, 0, 4, _("You may only select whole lists for Lcc")); else if(style == SelectAddrLccCom && dl->type != ListHead) q_status_message(SM_ORDER, 0, 4, _("You may only select lists for Lcc, use Bcc for personal entries")); else if(dl->type == ListHead || dl->type == Simple){ current_changed_flag++; if(entry_is_checked(pab->address_book->checks, (a_c_arg_t)dl->elnum)){ entry_unset_checked(pab->address_book->checks, (a_c_arg_t)dl->elnum); checkedn--; if(checkedn == 0) setall_changed++; } else{ entry_set_checked(pab->address_book->checks, (a_c_arg_t)dl->elnum); if(checkedn == 0) setall_changed++; checkedn++; } } else q_status_message(SM_ORDER, 0, 4, _("You may not select list members, only whole lists or personal entries")); } else q_status_message(SM_ORDER, 0, 4, _("You may only select addresses or lists")); break; /*------ Turn all checkboxes on ---------*/ case MC_SELALL: selall: if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to select")); break; } if(entry_is_clickable(as.top_ent+as.cur_row)){ clickable_warning(as.top_ent+as.cur_row); break; } if(empty){ empty_warning(as.top_ent+as.cur_row); break; } { adrbk_cntr_t num, ab_count; ab_count = adrbk_count(pab->address_book); setall_changed++; if(checkedn){ /* unset All */ for(num = 0; num < ab_count; num++){ if(entry_is_checked(pab->address_book->checks, (a_c_arg_t)num)){ entry_unset_checked(pab->address_book->checks, (a_c_arg_t)num); checkedn--; } } } else{ /* set All */ for(num = 0; num < ab_count; num++){ if(!entry_is_checked(pab->address_book->checks, (a_c_arg_t)num)){ entry_set_checked(pab->address_book->checks, (a_c_arg_t)num); checkedn++; } } } ps->mangled_body = 1; start_disp = 0; } break; /*---------- Turn on ListMode -----------*/ case MC_LISTMODE: as.checkboxes = 1; for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; init_disp_form(pab, ps->VAR_ABOOK_FORMATS, i); } (void)calculate_field_widths(); ps->mangled_footer = 1; ps->mangled_body = 1; start_disp = 0; q_status_message(SM_ORDER, 0, 4, _("Use \"X\" to select addresses or lists")); break; /*--------- Compose -----------*/ case MC_COMPOSE: (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0); break; /*--------- Alt Compose -----------*/ case MC_ROLE: (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1); break; #ifdef ENABLE_LDAP /*------ Query Directory ------*/ case MC_QUERY_SERV: q_server: {char *err_mess = NULL, **err_mess_p; int query_type; if(!directory_ok){ q_status_message(SM_ORDER, 0, 4, (style == SelectAddrLccCom) ? "Can't search server for Lcc" : "Can't search server from here"); break; } else if(as.checkboxes){ q_status_message(SM_ORDER, 0, 4, "Can't search server when using ListMode"); break; } if(error_message) err_mess_p = error_message; else err_mess_p = &err_mess; /* * The query lines are indexed by their adrbk_num. (Just using * that because it was handy and not used for addrbooks.) */ query_type = adrbk_num_from_lineno(as.top_ent+as.cur_row); if((addr = query_server(ps, are_selecting, &quit, query_type, err_mess_p)) != NULL){ if(are_selecting) return(addr); fs_give((void **)&addr); } if(err_mess_p && *err_mess_p && !error_message){ q_status_message(SM_ORDER, 0, 4, *err_mess_p); fs_give((void **)err_mess_p); } } break; #endif /* ENABLE_LDAP */ /*----------- Where is (search) ----------------*/ case MC_WHEREIS: warped = 0; new_top_ent = ab_whereis(&warped, command_line); if(new_top_ent != NO_LINE){ if(warped || new_top_ent != as.top_ent){ as.top_ent = new_top_ent; start_disp = 0; ps->mangled_body = 1; } else current_changed_flag++; } ps->mangled_footer = 1; break; /*----- Select entries to work on --*/ case MC_SELECT: if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to select")); break; } if(!cur_is_open()){ if(entry_is_askserver(as.top_ent+as.cur_row)) q_status_message(SM_ORDER, 0, 4, _("Select is only available from within an expanded address book")); else clickable_warning(as.top_ent+as.cur_row); break; } dl = dlist(as.top_ent+as.cur_row); if(dl->type == Empty){ empty_warning(as.top_ent+as.cur_row); break; } {int were_selections = as.selections; ab_select(ps, pab ? pab->address_book : NULL, as.top_ent+as.cur_row, command_line, &start_disp); if((!were_selections && as.selections) || (were_selections && !as.selections)){ ps->mangled_footer = 1; for(i = 0; i < as.n_addrbk; i++) init_disp_form(&as.adrbks[i], ps->VAR_ABOOK_FORMATS, i); } } break; /*----------- Select current entry ----------*/ case MC_SELCUR: if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to select")); break; } if(entry_is_clickable(as.top_ent+as.cur_row)){ clickable_warning(as.top_ent+as.cur_row); break; } if(empty){ empty_warning(as.top_ent+as.cur_row); break; } if(is_addr(as.top_ent+as.cur_row)){ dl = dlist(as.top_ent+as.cur_row); if(dl->type == ListHead || dl->type == Simple){ int do_init_disp = 0; long ll; current_changed_flag++; if(entry_is_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum)){ DL_CACHE_S *dlc, dlc_restart; as.selections--; if(as.selections == 0) do_init_disp++; entry_unset_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum); if(as.zoomed){ dlc = get_dlc(as.top_ent+as.cur_row); if(as.selections){ flush_dlc_from_cache(dlc); dlc = get_dlc(as.top_ent+as.cur_row); ps->mangled_body = 1; if(dlc->type == DlcEnd){ r = prev_selectable_line(as.cur_row + as.top_ent, &new_line); if(r){ as.cur_row = new_line - as.top_ent; if(as.cur_row < 0){ as.top_ent += as.cur_row; as.cur_row = 0; start_disp = 0; } else start_disp = as.cur_row; } } else start_disp = MAX(as.cur_row-1,0); } else{ dlc_restart = *dlc; as.zoomed = 0; q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now off, no entries selected")); warp_to_dlc(&dlc_restart, 0L); /* put current entry in middle of screen */ as.top_ent = first_line(0L - (long)as.l_p_page/2L); as.cur_row = 0L - as.top_ent; ps->mangled_body = 1; start_disp = 0; } } else if(F_OFF(F_UNSELECT_WONT_ADVANCE,ps_global)){ r = next_selectable_line(as.cur_row+as.top_ent, &new_line); if(r){ for(ll = new_line; (dl=dlist(ll))->type != End; ll++) if(dl->type == ListHead || dl->type == Simple) break; if(dl->type != End) new_line = ll; as.cur_row = new_line - as.top_ent; if(as.cur_row >= as.l_p_page){ /*-- Changed pages --*/ as.top_ent += as.l_p_page; as.cur_row -= as.l_p_page; /* if it is still off screen */ if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row-as.l_p_page+1); as.cur_row = (as.l_p_page - 1); } start_disp = 0; ps->mangled_body = 1; } } } } else{ if(as.selections == 0) do_init_disp++; as.selections++; entry_set_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum); r = next_selectable_line(as.cur_row+as.top_ent, &new_line); if(r){ for(ll = new_line; (dl=dlist(ll))->type != End; ll++) if(dl->type == ListHead || dl->type == Simple) break; if(dl->type != End) new_line = ll; as.cur_row = new_line - as.top_ent; if(as.cur_row >= as.l_p_page){ /*-- Changed pages --*/ as.top_ent += as.l_p_page; as.cur_row -= as.l_p_page; /* if it is still off screen */ if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row-as.l_p_page+1); as.cur_row = (as.l_p_page - 1); } start_disp = 0; ps->mangled_body = 1; } } } /* * If we switch from selected to non-selected or * vice versa, we have to init_disp_form() for all * the addrbooks, in case we're using the X instead * of bold. */ if(do_init_disp){ ps->mangled_footer = 1; for(i = 0; i < as.n_addrbk; i++) init_disp_form(&as.adrbks[i], ps->VAR_ABOOK_FORMATS, i); } } else q_status_message(SM_ORDER, 0, 4, _("You may not select list members, only whole lists or personal entries")); } else q_status_message(SM_ORDER, 0, 4, _("You may only select addresses or lists")); break; /*--- Zoom in and look only at selected entries (or zoom out) --*/ case MC_ZOOM: as.zoomed = (1 - as.zoomed); if(as.zoomed) ab_zoom((pab && pab->address_book) ? pab->address_book->selects : NULL, &start_disp); else{ q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now off")); ab_unzoom(&start_disp); } break; /*--- Apply a command -----------*/ case MC_APPLY: if(as.selections){ if(((ab_apply_cmd(ps, pab ? pab->address_book : NULL, as.top_ent+as.cur_row, command_line) && F_ON(F_AUTO_UNZOOM, ps)) || !as.selections) && as.zoomed){ ab_unzoom(NULL); ps_global->mangled_body = 1; } /* * In case the line we're now at is not a selectable * field. * * We set start_disp to zero here but rely on the called * routine to set mangled_body if appropriate. */ start_disp = 0; new_line = first_selectable_line(as.cur_row+as.top_ent); if(new_line != NO_LINE && new_line != as.cur_row+as.top_ent){ as.cur_row = new_line - as.top_ent; if(as.cur_row < 0){ as.top_ent += as.cur_row; as.cur_row = 0; } else if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row - as.l_p_page + 1); as.cur_row = as.l_p_page - 1; } } } else q_status_message(SM_ORDER, 0, 2, _("No selected entries to apply command to")); break; /*--------- QUIT pine -----------*/ case MC_QUIT: dprint((7, "Quitting pine from addrbook\n")); ps->next_screen = quit_screen; break; /*--------- Top of Folder list -----------*/ case MC_COLLECTIONS: dprint((7, "Goto folder lister from addrbook\n")); ps->next_screen = folder_screen; break; /*---------- Open specific new folder ----------*/ case MC_GOTO: dprint((7, "Goto from addrbook\n")); ab_goto_folder(command_line); break; /*--------- Index -----------*/ case MC_INDEX: dprint((7, "Goto message index from addrbook\n")); if(THREADING() && sp_viewing_a_thread(ps->mail_stream) && unview_thread(ps, ps->mail_stream, ps->msgmap)){ ps->next_screen = mail_index_screen; ps->view_skipped_index = 0; ps->mangled_screen = 1; } ps->next_screen = mail_index_screen; break; /*----------------- Print --------------------*/ case MC_PRINTTXT: (void)ab_print(0); ps->mangled_screen = 1; break; /*------ Copy entries into an abook ----*/ case MC_SAVE: if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to save")); break; } if(entry_is_clickable(as.top_ent+as.cur_row)){ clickable_warning(as.top_ent+as.cur_row); break; } if(empty){ empty_warning(as.top_ent+as.cur_row); break; } (void)ab_save(ps, pab ? pab->address_book : NULL, as.top_ent+as.cur_row, command_line, 0); break; /*------ Forward an entry in mail -----------*/ case MC_FORWARD: if(!any_addrs_avail(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("No entries to forward")); break; } if(entry_is_clickable(as.top_ent+as.cur_row)){ clickable_warning(as.top_ent+as.cur_row); break; } if(empty){ empty_warning(as.top_ent+as.cur_row); break; } if(!is_addr(as.top_ent+as.cur_row)){ q_status_message(SM_ORDER, 0, 4, _("Nothing to forward")); break; } dl = dlist(as.top_ent+as.cur_row); if(dl->type != ListHead && dl->type != Simple){ q_status_message(SM_ORDER, 0, 4, _("Can only forward whole entries")); break; } (void)ab_forward(ps, as.top_ent+as.cur_row, 0); ps->mangled_footer = 1; break; case MC_REPAINT: /* ^L attempts to resynchronize with changed addrbooks */ if(adrbk_check_all_validity_now()) (void)resync_screen(pab, style, checkedn); /* fall through */ case MC_RESIZE: mark_status_dirty(); mark_titlebar_dirty(); mark_keymenu_dirty(); ClearBody(); ps->mangled_screen = 1; if(c == KEY_RESIZE) ab_resize(); break; case MC_UTF8: bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps) ? "F1" : "?"); break; /*------ Some back compatibility messages -----*/ case MC_UNKNOWN: if(c == 'e' && !are_selecting){ q_status_message(SM_ORDER | SM_DING, 0, 2, _("Command \"E\" not defined. Use \"View/Update\" to edit an entry")); break; } else if(c == 's' && !(are_selecting || entry_is_clickable(as.top_ent+as.cur_row))){ q_status_message(SM_ORDER | SM_DING, 0, 2, _("Command \"S\" not defined. Use \"AddNew\" to create a list")); break; } else if(c == 'z' && !are_selecting){ q_status_message(SM_ORDER | SM_DING, 0, 2, _("Command \"Z\" not defined. Use \"View/Update\" to add to a list")); break; } /* else, fall through */ default: bogus_command(c, F_ON(F_USE_FK, ps) ? "F1" : "?"); break; } if(ps->next_screen != SCREEN_FUN_NULL) quit++; } erase_selections(); return NULL; } /* * Turn on zoom mode and zoom in if applicable. * * Args selecteds -- tells which entries are selected in current abook * start_disp -- Passed in so we can set it back in the caller */ void ab_zoom(EXPANDED_S *selecteds, int *start_disp) { AddrScrn_Disp *dl; DL_CACHE_S *dlc, dlc_restart; as.zoomed = 1; if(as.selections){ q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now on")); if(cur_is_open()){ dl = dlist(as.top_ent+as.cur_row); if((dl->type == ListHead || dl->type == Simple || dl->type == ListEmpty || dl->type == ListClickHere || dl->type == ListEnt) && entry_is_selected(selecteds, (a_c_arg_t)dl->elnum)){ dlc = get_dlc(as.top_ent+as.cur_row); dlc_restart = *dlc; warp_to_dlc(&dlc_restart, 0L); /* put current entry in middle of screen */ as.top_ent = first_line(0L - (long)as.l_p_page/2L); as.cur_row = 0L - as.top_ent; } else{ long new_ent; warp_to_top_of_abook(as.cur); as.top_ent = 0L; new_ent = first_selectable_line(0L); if(new_ent == NO_LINE) as.cur_row = 0L; else as.cur_row = new_ent; /* if it is off screen */ if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row - as.l_p_page + 1); as.cur_row = (as.l_p_page - 1); } } } else{ dlc = get_dlc(as.top_ent+as.cur_row); dlc_restart = *dlc; warp_to_dlc(&dlc_restart, 0L); /* put current entry in middle of screen */ as.top_ent = first_line(0L - (long)as.l_p_page/2L); as.cur_row = 0L - as.top_ent; } ps_global->mangled_body = 1; *start_disp = 0; } else{ as.zoomed = 0; q_status_message(SM_ORDER, 0, 2, _("No selected entries to zoom on")); } } /* * Turn off zoom mode and zoom out if applicable. * * Args start_disp -- Passed in so we can set it back in the caller */ void ab_unzoom(int *start_disp) { DL_CACHE_S *dlc, dlc_restart; as.zoomed = 0; dlc = get_dlc(as.top_ent+as.cur_row); if(dlc->type == DlcZoomEmpty){ long new_ent; warp_to_beginning(); as.top_ent = 0L; new_ent = first_selectable_line(0L); if(new_ent == NO_LINE) as.cur_row = 0L; else as.cur_row = new_ent; /* if it is off screen */ if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row - as.l_p_page + 1); as.cur_row = (as.l_p_page - 1); } } else{ dlc_restart = *dlc; warp_to_dlc(&dlc_restart, 0L); /* put current entry in middle of screen */ as.top_ent = first_line(0L - (long)as.l_p_page/2L); as.cur_row = 0L - as.top_ent; } ps_global->mangled_body = 1; if(start_disp) *start_disp = 0; } /* * Post an empty addrbook warning. * * Args: cur_line -- The current line position (in global display list) * of cursor */ void empty_warning(long int cur_line) { register AddrScrn_Disp *dl; dl = dlist(cur_line); if(dl->type == NoAbooks) q_status_message(SM_ORDER, 0, 4, _("No address books configured, use Setup")); else if(dl->type == Empty) q_status_message(SM_ORDER, 0, 4, _("Address Book is Empty")); else q_status_message(SM_ORDER, 0, 4, _("Distribution List is Empty")); } /* * Tell user to click on this to expand. * * Args: cur_line -- The current line position (in global display list) * of cursor */ void clickable_warning(long int cur_line) { register AddrScrn_Disp *dl; dl = dlist(cur_line); if(dl->type == Title || dl->type == ClickHereCmb) q_status_message(SM_ORDER, 0, 4, _("Address Book not expanded, use \">\" to expand")); else q_status_message(SM_ORDER, 0, 4, _("Distribution List not expanded, use \">\" to expand")); } /* * Post a no tabs warning. */ void no_tabs_warning(void) { q_status_message(SM_ORDER, 0, 4, "Tabs not allowed in address book"); } /* * Prompt for command to apply to selected entries * * Returns: 1 if the entries are successfully commanded. * 0 otherwise */ int ab_apply_cmd(struct pine *ps, AdrBk *abook, long int cur_line, int command_line) { int ret = 0; static ESCKEY_S opts[] = { {'c', 'c', "C", "ComposeTo"}, {'d', 'd', "D", "Delete"}, {'%', '%', "%", "Print"}, {'f', 'f', "F", "Forward"}, {'s', 's', "S", "Save"}, {'#', '#', "#", "Role"}, { 0, '%', "", ""}, {-1, 0, NULL, NULL}}; #define PHANTOM_PRINT 6 dprint((7, "- ab_apply_cmd -\n")); opts[PHANTOM_PRINT].ch = (F_ON(F_ENABLE_PRYNT, ps_global)) ? 'y' : -1; switch(radio_buttons("APPLY command : ", command_line, opts, 'z', 'x', NO_HELP, RB_NORM)){ case 'c': ret = ab_compose_to_addr(cur_line, 1, 0); break; case '#': ret = ab_compose_to_addr(cur_line, 1, 1); break; case 'd': ret = ab_agg_delete(ps, 1); break; case '%': ret = ab_print(1); break; case 'f': ret = ab_forward(ps, cur_line, 1); break; case 's': ret = ab_save(ps, abook, cur_line, command_line, 1); break; case 'x': cmd_cancelled("Apply command"); break; case 'z': q_status_message(SM_INFO, 0, 2, "Cancelled, there is no default command"); break; } ps_global->mangled_footer = 1; return(ret); } /* * Allow user to mark some entries "selected". */ void ab_select(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int *start_disp) { static ESCKEY_S sel_opts1[] = { {'a', 'a', "A", "unselect All"}, { 0 , 'c', "C", NULL}, {'b', 'b', "B", "Broaden selctn"}, {'n', 'n', "N", "Narrow selctn"}, {'f', 'f', "F", "Flip selected"}, {-1, 0, NULL, NULL} }; static char *sel_pmt1 = "ALTER selection : "; static ESCKEY_S sel_opts2[] = { {'a', 'a', "A", "select All"}, {'c', 'c', "C", "select Cur"}, {'t', 't', "T", "Text"}, {'s', 's', "S", "Status"}, {-1, 0, NULL, NULL} }; static char *sel_pmt2 = "SELECT criteria : "; ESCKEY_S *sel_opts; HelpType help = NO_HELP; adrbk_cntr_t num, ab_count; int q = 0, rv = 0, narrow = 0, do_flush = 0, do_warp = 0, prevsel, move_current = 0, do_beginning = 0; long new_ent; AddrScrn_Disp *dl; DL_CACHE_S *dlc, dlc_restart; dprint((5, "- ab_select -\n")); if(cur_is_open()){ /* select applies only to this addrbook */ ps->mangled_footer = 1; prevsel = as.selections; dl = dlist(cur_line); sel_opts = sel_opts2; /* * If already some selected, ask how to alter that selection. */ if(as.selections){ sel_opts += 2; /* don't offer all or current below */ if(dl && (dl->type == ListHead || dl->type == Simple)){ sel_opts1[1].label = entry_is_selected(abook->selects, (a_c_arg_t)dl->elnum) ? "unselect Cur" : "select Cur"; sel_opts1[1].ch = 'c'; } else sel_opts1[1].ch = -2; /* don't offer this choice */ switch(q = radio_buttons(sel_pmt1, command_line, sel_opts1, 'a', 'x', help, RB_NORM)){ case 'n': /* narrow selection */ narrow++; case 'b': /* broaden selection */ q = 0; /* but don't offer criteria prompt */ break; case 'c': /* select or unselect current */ case 'a': /* select or unselect all */ case 'f': /* flip selections */ case 'x': /* cancel */ break; default: q_status_message(SM_ORDER | SM_DING, 3, 3, "Unsupported Select option"); return; } } if(abook && dl && (dl->type == ListHead || dl->type == Simple)){ sel_opts1[1].label = entry_is_selected(abook->selects, (a_c_arg_t)dl->elnum) ? "unselect Cur" : "select Cur"; sel_opts1[1].ch = 'c'; } else sel_opts1[1].ch = -2; /* don't offer this choice */ if(!q) q = radio_buttons(sel_pmt2, command_line, sel_opts, 'c', 'x', help, RB_NORM); *start_disp = 0; dlc = get_dlc(cur_line); dlc_restart = *dlc; ab_count = adrbk_count(abook); switch(q){ case 'x': /* cancel */ cmd_cancelled("Select command"); break; case 'c': /* select/unselect current */ ps->mangled_body = 1; if(entry_is_selected(abook->selects, (a_c_arg_t)dl->elnum)){ entry_unset_selected(abook->selects, (a_c_arg_t)dl->elnum); as.selections--; if(as.selections == 0 && as.zoomed){ as.zoomed = 0; q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off, no entries selected"); do_warp++; } else if(as.zoomed){ move_current++; do_flush++; } else do_flush++; } else{ entry_set_selected(abook->selects, (a_c_arg_t)dl->elnum); as.selections++; if(as.selections == 1 && !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){ as.zoomed = 1; as.top_ent = dlc_restart.global_row; as.cur_row = 0; do_warp++; } else do_flush++; } break; case 'a': ps->mangled_body = 1; if(any_selected(abook->selects)){ /* unselect all */ for(num = 0; num < ab_count; num++){ if(entry_is_selected(abook->selects, (a_c_arg_t)num)){ as.selections--; entry_unset_selected(abook->selects, (a_c_arg_t)num); } } if(as.selections == 0 && as.zoomed){ as.zoomed = 0; q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off, all entries UNselected"); do_warp++; } else{ char bb[100]; snprintf(bb, sizeof(bb), "%s entries UNselected%s%s%s", comatose(prevsel-as.selections), as.selections ? ", still " : "", as.selections ? comatose(as.selections) : "", as.selections ? " selected in other addrbooks" : ""); bb[sizeof(bb)-1] = '\0'; q_status_message(SM_ORDER, 0, 2, bb); if(as.zoomed) do_beginning++; else do_flush++; } } else{ /* select all */ for(num = 0; num < ab_count; num++){ if(!entry_is_selected(abook->selects, (a_c_arg_t)num)){ as.selections++; entry_set_selected(abook->selects, (a_c_arg_t)num); } } q_status_message1(SM_ORDER, 0, 2, "All %s entries selected", comatose(ab_count)); if(prevsel == 0 && as.selections > 0 && !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){ as.zoomed = 1; as.top_ent = dlc_restart.global_row - as.cur_row; do_warp++; } else if(dlc_restart.type == DlcZoomEmpty && as.selections > prevsel) do_beginning++; else if(as.zoomed) do_warp++; else do_flush++; } break; case 'f': /* flip selections in this abook */ ps->mangled_body = 1; for(num = 0; num < ab_count; num++){ if(entry_is_selected(abook->selects, (a_c_arg_t)num)){ entry_unset_selected(abook->selects, (a_c_arg_t)num); as.selections--; } else{ entry_set_selected(abook->selects, (a_c_arg_t)num); as.selections++; } } if(as.zoomed){ if(as.selections) do_beginning++; else{ as.zoomed = 0; q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off"); do_warp++; } } else do_warp++; q_status_message1(SM_ORDER, 0, 2, "%s entries now selected", comatose(as.selections)); break; case 't': case 's': switch(q){ case 't': rv = ab_select_text(abook, narrow); break; case 's': rv = ab_select_type(abook, narrow); break; } if(!rv){ ps->mangled_body = 1; if(dlc_restart.type == DlcZoomEmpty && as.selections > prevsel) do_beginning++; else if(as.zoomed){ if(as.selections == 0){ as.zoomed = 0; q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off"); do_warp++; } else do_beginning++; } else{ if(prevsel == 0 && as.selections > 0 && !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){ as.zoomed = 1; do_beginning++; } else do_warp++; } if(prevsel == as.selections && prevsel > 0){ if(as.selections == 1) q_status_message(SM_ORDER, 0, 2, "No change resulted, 1 entry remains selected"); else q_status_message1(SM_ORDER, 0, 2, "No change resulted, %s entries remain selected", comatose(as.selections)); } else if(prevsel == 0){ if(as.selections == 1) q_status_message(SM_ORDER, 0, 2, "Select matched 1 entry"); else if(as.selections > 1) q_status_message1(SM_ORDER, 0, 2, "Select matched %s entries", comatose(as.selections)); else q_status_message(SM_ORDER, 0, 2, "Select failed! No entries selected"); } else if(as.selections == 0){ if(prevsel == 1) q_status_message(SM_ORDER, 0, 2, "The single selected entry is UNselected"); else q_status_message1(SM_ORDER, 0, 2, "All %s entries UNselected", comatose(prevsel)); } else if(narrow){ if(as.selections == 1 && (prevsel-as.selections) == 1) q_status_message(SM_ORDER, 0, 2, "1 entry now selected, 1 entry was UNselected"); else if(as.selections == 1) q_status_message1(SM_ORDER, 0, 2, "1 entry now selected, %s entries were UNselected", comatose(prevsel-as.selections)); else if((prevsel-as.selections) == 1) q_status_message1(SM_ORDER, 0, 2, "%s entries now selected, 1 entry was UNselected", comatose(as.selections)); else q_status_message2(SM_ORDER, 0, 2, "%s entries now selected, %s entries were UNselected", comatose(as.selections), comatose(prevsel-as.selections)); } else{ if((as.selections-prevsel) == 1) q_status_message1(SM_ORDER, 0, 2, "1 new entry selected, %s entries now selected", comatose(as.selections)); else if(as.selections == 1) q_status_message1(SM_ORDER, 0, 2, "%s new entries selected, 1 entry now selected", comatose(as.selections-prevsel)); else q_status_message2(SM_ORDER, 0, 2, "%s new entries selected, %s entries now selected", comatose(as.selections-prevsel), comatose(as.selections)); } } break; default : q_status_message(SM_ORDER | SM_DING, 3, 3, "Unsupported Select option"); break; } } else{ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)) q_status_message(SM_ORDER | SM_DING, 3, 3, "Select is only available from within an expanded address book"); else q_status_message(SM_ORDER | SM_DING, 3, 3, "Select is only available when viewing an individual address book"); return; } if(rv) return; if(do_beginning){ warp_to_beginning(); /* just go to top */ as.top_ent = 0L; new_ent = first_selectable_line(0L); if(new_ent == NO_LINE) as.cur_row = 0L; else as.cur_row = new_ent; /* if it is off screen */ if(as.cur_row >= as.l_p_page){ as.top_ent += (as.cur_row - as.l_p_page + 1); as.cur_row = (as.l_p_page - 1); } } else if(do_flush) flush_dlc_from_cache(&dlc_restart); else if(do_warp) warp_to_dlc(&dlc_restart, dlc_restart.global_row); if(move_current){ dlc = get_dlc(cur_line); if(dlc->type == DlcEnd){ as.cur_row--; if(as.cur_row < 0){ as.top_ent += as.cur_row; /* plus a negative number */ as.cur_row = 0; *start_disp = 0; } else *start_disp = as.cur_row; } else *start_disp = as.cur_row; } } /* * Selects based on whether an entry is a list or not. * * Returns: 0 if search went ok * -1 if there was a problem */ int ab_select_type(AdrBk *abook, int narrow) { static ESCKEY_S ab_sel_type_opt[] = { {'s', 's', "S", "Simple"}, {'l', 'l', "L", "List"}, {-1, 0, NULL, NULL} }; static char *ab_sel_type = "Select Lists or Simples (non Lists) ? "; int type; adrbk_cntr_t num, ab_count; dprint((6, "- ab_select_type -\n")); if(!abook) return -1; switch(type = radio_buttons(ab_sel_type, -FOOTER_ROWS(ps_global), ab_sel_type_opt, 'l', 'x', NO_HELP, RB_NORM)){ case 'l': break; case 's': break; case 'x': cmd_cancelled("Select"); return -1; default: dprint((1,"\n - BOTCH: ab_select_type unknown option\n")); return -1; } ab_count = adrbk_count(abook); for(num = 0; num < ab_count; num++){ AdrBk_Entry *abe; int matched; /* * If it won't possibly change state, don't look at it. */ if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) || (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num))) continue; abe = adrbk_get_ae(abook, (a_c_arg_t) num); matched = ((type == 's' && abe->tag == Single) || (type == 'l' && abe->tag == List)); if(narrow && !matched){ entry_unset_selected(abook->selects, (a_c_arg_t)num); as.selections--; } else if(!narrow && matched){ entry_set_selected(abook->selects, (a_c_arg_t)num); as.selections++; } } return 0; } /* * Selects based on string matches in various addrbook fields. * * Returns: 0 if search went ok * -1 if there was a problem */ int ab_select_text(AdrBk *abook, int narrow) { static ESCKEY_S ab_sel_text_opt[] = { {'n', 'n', "N", "Nickname"}, {'a', 'a', "A", "All Text"}, {'f', 'f', "F", "Fullname"}, {'e', 'e', "E", "Email Addrs"}, {'c', 'c', "C", "Comment"}, {'z', 'z', "Z", "Fcc"}, {-1, 0, NULL, NULL} }; static char *ab_sel_text = "Select based on Nickname, All text, Fullname, Addrs, Comment, or Fcc ? "; HelpType help = NO_HELP; int type, r; char sstring[80+1], prompt[80]; adrbk_cntr_t num, ab_count; char *fmt = "String in \"%s\" to match : "; dprint((6, "- ab_select_text -\n")); if(!abook) return -1; switch(type = radio_buttons(ab_sel_text, -FOOTER_ROWS(ps_global), ab_sel_text_opt, 'a', 'x', NO_HELP, RB_NORM)){ case 'n': snprintf(prompt, sizeof(prompt), fmt, "Nickname"); break; case 'a': snprintf(prompt, sizeof(prompt), fmt, "All Text"); break; case 'f': snprintf(prompt, sizeof(prompt), fmt, "Fullname"); break; case 'e': snprintf(prompt, sizeof(prompt), fmt, "addresses"); break; case 'c': snprintf(prompt, sizeof(prompt), fmt, "Comment"); break; case 'z': snprintf(prompt, sizeof(prompt), fmt, "Fcc"); break; case 'x': break; default: dprint((1,"\n - BOTCH: ab_select_text unknown option\n")); return -1; } prompt[sizeof(prompt)-1] = '\0'; sstring[0] = '\0'; while(type != 'x'){ int flags = OE_APPEND_CURRENT; r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0, sizeof(sstring), prompt, NULL, help, &flags); switch(r){ case 3: /* BUG, no help */ case 4: continue; default: break; } if(r == 1 || sstring[0] == '\0') r = 'x'; break; } if(type == 'x' || r == 'x'){ cmd_cancelled("Select"); return -1; } ab_count = adrbk_count(abook); for(num = 0; num < ab_count; num++){ AdrBk_Entry *abe; int matched; /* * If it won't possibly change state, don't look at it. */ if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) || (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num))) continue; abe = adrbk_get_ae(abook, (a_c_arg_t) num); matched = match_check(abe, type, sstring); if(narrow && !matched){ entry_unset_selected(abook->selects, (a_c_arg_t)num); as.selections--; } else if(!narrow && matched){ entry_set_selected(abook->selects, (a_c_arg_t)num); as.selections++; } } return 0; } /* * Returns: 1 if a match is found for the entry * 0 no match * -1 error */ int match_check(AdrBk_Entry *abe, int type, char *string) { static int err = -1; static int match = 1; static int nomatch = 0; unsigned int checks; #define CK_NICKNAME 0x01 #define CK_FULLNAME 0x02 #define CK_ADDRESSES 0x04 #define CK_FCC 0x08 #define CK_COMMENT 0x10 #define CK_ALL 0x1f checks = 0; switch(type){ case 'n': /* Nickname */ checks |= CK_NICKNAME; break; case 'f': /* Fullname */ checks |= CK_FULLNAME; break; case 'e': /* Addrs */ checks |= CK_ADDRESSES; break; case 'a': /* All Text */ checks |= CK_ALL; break; case 'z': /* Fcc */ checks |= CK_FCC; break; case 'c': /* Comment */ checks |= CK_COMMENT; break; default: q_status_message(SM_ORDER | SM_DING, 3, 3, "Unknown type"); return(err); } if(checks & CK_NICKNAME){ if(abe && abe->nickname && srchstr(abe->nickname, string)) return(match); } if(checks & CK_FULLNAME){ if(abe && abe->fullname && abe->fullname[0] && srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, abe->fullname), string)) return(match); } if(checks & CK_ADDRESSES){ if(abe && abe->tag == Single && abe->addr.addr && abe->addr.addr[0] && srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, abe->addr.addr), string)) return(match); else if(abe && abe->tag == List && abe->addr.list){ char **p; for(p = abe->addr.list; p != NULL && *p != NULL; p++){ if(srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, *p), string)) return(match); } } } if(checks & CK_FCC){ if(abe && abe->fcc && abe->fcc[0] && srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, abe->fcc), string)) return(match); } if(checks & CK_COMMENT && abe && abe->extra && abe->extra[0]){ size_t n, len; unsigned char *p, *tmp = NULL; int found_it = 0; if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){ len = n+1; p = tmp = (unsigned char *)fs_get(len * sizeof(char)); } else{ len = SIZEOF_20KBUF; p = (unsigned char *)tmp_20k_buf; } if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra), string)) found_it++; if(tmp) fs_give((void **)&tmp); if(found_it) return(match); } return(nomatch); } /* * Go to folder. * * command_line -- The screen line on which to prompt */ void ab_goto_folder(int command_line) { char *go_folder; CONTEXT_S *tc; int notrealinbox = 0; dprint((2, "- ab_goto_folder -\n")); tc = ps_global->context_current; go_folder = broach_folder(command_line, 1, ¬realinbox, &tc); if(go_folder != NULL) visit_folder(ps_global, go_folder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT); } /* * Execute whereis command. * * Returns value of the new top entry, or NO_LINE if cancelled. */ long ab_whereis(int *warped, int command_line) { int rc, wrapped = 0; long new_top_ent, new_line; dprint((5, "- ab_whereis -\n")); rc = search_book(as.top_ent+as.cur_row, command_line, &new_line, &wrapped, warped); new_top_ent = NO_LINE; if(rc == -2) q_status_message(SM_INFO, 0, 2, _("Address book search cancelled")); else if(rc == -1) q_status_message(SM_ORDER, 0, 4, _("Word not found")); else if(rc == 0){ /* search succeeded */ if(wrapped == 1) q_status_message(SM_INFO, 0, 2, _("Search wrapped to beginning")); else if(wrapped == 2) q_status_message(SM_INFO, 0, 2, _("Current line contains the only match")); /* know match is on the same page */ if(!*warped && new_line >= as.top_ent && new_line < as.top_ent+as.l_p_page) new_top_ent = as.top_ent; /* don't know whether it is or not, reset top_ent */ else new_top_ent = first_line(new_line - as.l_p_page/2); as.cur_row = new_line - new_top_ent; } return(new_top_ent); } /* * recalculate display parameters for window size change */ void ab_resize(void) { long new_line; int old_l_p_p; DL_CACHE_S dlc_buf, *dlc_restart; old_l_p_p = as.l_p_page; as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global) - HEADER_ROWS(ps_global); dprint((9, "- ab_resize -\n l_p_p was %d, now %d\n", old_l_p_p, as.l_p_page)); if(as.l_p_page <= 0){ as.no_op_possbl++; return; } else as.no_op_possbl = 0; new_line = as.top_ent + as.cur_row; as.top_ent = first_line(new_line - as.l_p_page/2); as.cur_row = new_line - as.top_ent; as.old_cur_row = as.cur_row; /* need this to re-initialize Text and Title lines in display */ /* get the old current line (which may be the wrong width) */ dlc_restart = get_dlc(new_line); /* flush it from cache */ flush_dlc_from_cache(dlc_restart); /* re-get it (should be right now) */ dlc_restart = get_dlc(new_line); /* copy it to local storage */ dlc_buf = *dlc_restart; dlc_restart = &dlc_buf; /* flush everything from cache and add that one line back in */ warp_to_dlc(dlc_restart, new_line); } /* * Returns 0 if we know for sure that there are no * addresses available in any of the addressbooks. * * Easiest would be to start at 0 and go through the addrbook, but that will * be very slow for big addrbooks if we're not close to 0 already. Instead, * starting_hint is a hint at a good place to start looking. */ int any_addrs_avail(long int starting_hint) { register AddrScrn_Disp *dl; long lineno; /* * Look from lineno backwards first, in hopes of finding it in cache. */ lineno = starting_hint; for(dl=dlist(lineno); dl->type != Beginning; dl = dlist(--lineno)){ if(dl->type == NoAbooks) return 0; switch(dl->type){ case Simple: case ListEnt: case ListHead: case ZoomEmpty: case Title: case ListClickHere: case ClickHereCmb: return 1; default: break; } } /* search from here forward if we still don't know */ lineno = starting_hint; for(dl=dlist(lineno); dl->type != End; dl = dlist(++lineno)){ if(dl->type == NoAbooks) return 0; switch(dl->type){ case Simple: case ListEnt: case ListHead: case ZoomEmpty: case Title: case ListClickHere: case ClickHereCmb: return 1; default: break; } } return 0; } /* * Returns 1 if this line is a clickable line. */ int entry_is_clickable(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && (dl->type == Title || dl->type == ListClickHere || dl->type == ClickHereCmb)) return 1; return 0; } /* * Returns 1 if this line is a clickable Title line. */ int entry_is_clickable_title(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && (dl->type == Title || dl->type == ClickHereCmb)) return 1; return 0; } /* * Returns 1 if an address or list is selected. */ int is_addr(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && (dl->type == ListHead || dl->type == ListEnt || dl->type == Simple)) return 1; return 0; } /* * Returns 1 if type of line is Empty. */ int is_empty(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && (dl->type == Empty || dl->type == ListEmpty || dl->type == ZoomEmpty)) return 1; return 0; } /* * Returns 1 if lineno is a list entry */ int entry_is_listent(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && dl->type == ListEnt) return 1; return 0; } /* * Returns 1 if lineno is a fake addrbook for config screen add * or if it is an AskServer line for LDAP query */ int entry_is_addkey(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && (dl->type == AddFirstPers || dl->type == AddFirstGlob || dl->type == AskServer)) return 1; return 0; } /* * Returns 1 if lineno is the line to ask for directory server query */ int entry_is_askserver(long int lineno) { register AddrScrn_Disp *dl; if((dl = dlist(lineno)) && dl->type == AskServer) return 1; return 0; } /* * Returns 1 if an add abook here would be global, not personal */ int add_is_global(long int lineno) { register AddrScrn_Disp *dl; dl = dlist(lineno); if(dl){ if(dl->type == Title){ register PerAddrBook *pab; pab = &as.adrbks[as.cur]; if(pab && pab->type & GLOBAL) return 1; } else if(dl->type == AddFirstGlob) return 1; } return 0; } /* * Find the first line greater than or equal to line. (Any line, not * necessarily selectable.) * * Returns the line number of the found line or NO_LINE if there is none. * * Warning: This just starts at the passed in line and goes forward until * it runs into a line that isn't a Beginning line. If the line passed in * is not in the dlc cache, it will have no way to know when it gets to the * real beginning. */ long first_line(long int line) { long lineno; register PerAddrBook *pab; int i; for(lineno=line; dlist(lineno)->type == Beginning; lineno++) ;/* do nothing */ if(dlist(lineno)->type != End) return(lineno); else{ for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->ostatus != Open && pab->ostatus != HalfOpen && pab->ostatus != ThreeQuartOpen) return NO_LINE; } as.no_op_possbl++; return(NO_LINE); } } /* * Find the line number of the next selectable line. * * Args: cur_line -- The current line position (in global display list) * of cursor * new_line -- Return value: new line position * * Result: The new line number is set. * The value 1 is returned if OK or 0 if there is no next line. */ int next_selectable_line(long int cur_line, long int *new_line) { /* skip over non-selectable lines */ for(cur_line++; !line_is_selectable(cur_line) && dlist(cur_line)->type != End; cur_line++) ;/* do nothing */ if(dlist(cur_line)->type == End) return 0; *new_line = cur_line; return 1; } /* * Find the line number of the previous selectable line. * * Args: cur_line -- The current line position (in global display list) * of cursor * new_line -- Return value: new line position * * Result: The new line number is set. * The value 1 is returned if OK or 0 if there is no previous line. */ int prev_selectable_line(long int cur_line, long int *new_line) { /* skip backwards over non-selectable lines */ for(cur_line--; !line_is_selectable(cur_line) && dlist(cur_line)->type != Beginning; cur_line--) ;/* do nothing */ if(dlist(cur_line)->type == Beginning) return 0; *new_line = cur_line; return 1; } /* * Resync the display with the addrbooks, which were just discovered * to be out of sync with the display. * * Returns 1 -- current address book had to be resynced * 0 -- current address book not resynced */ int resync_screen(PerAddrBook *pab, AddrBookArg style, int checkedn) { AddrScrn_Disp *dl; int current_resynced = 0; DL_CACHE_S dlc_restart, *dlc = NULL; /* * The test below gives conditions under which it is safe to go ahead * and resync all the addrbooks that are out of sync now, and the * display won't change. Otherwise, we have to be careful to preserve * some of our state so that we can attempt to restore the screen to * a state that is as close as possible to what we have now. If the * currently opened address book (pab) is out of date we will lose * the expanded state of its distribution lists, which is no big deal. * Since resyncing also loses the checked status if we're selecting with * ListMode, we don't even attempt it in that case. */ if((ab_nesting_level < 2 && !cur_is_open() && checkedn == 0) || (style == AddrBookScreen && pab && pab->address_book && !(pab->address_book->flags & FILE_OUTOFDATE || (pab->address_book->rd && pab->address_book->rd->flags & REM_OUTOFDATE)))){ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ dlc = get_dlc(as.top_ent+as.cur_row); dlc_restart = *dlc; } if(adrbk_check_and_fix_all(1, 0, 1)){ ps_global->mangled_footer = 1; /* why? */ if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){ warp_to_dlc(&dlc_restart, 0L); /* put current entry in middle of screen */ as.top_ent = first_line(0L - (long)as.l_p_page/2L); as.cur_row = 0L - as.top_ent; ps_global->mangled_screen = 1; } } } else if(style == AddrBookScreen){ char *savenick = NULL; AdrBk_Entry *abe; adrbk_cntr_t old_entry_num, new_entry_num; long old_global_row; current_resynced++; /* * We're going to try to get the nickname of the current * entry and find it again after the resync. */ dl = dlist(as.top_ent+as.cur_row); if(dl->type == ListEnt || dl->type == ListEmpty || dl->type == ListClickHere || dl->type == ListHead || dl->type == Simple){ abe = ae(as.top_ent+as.cur_row); old_entry_num = dl->elnum; old_global_row = as.top_ent+as.cur_row; if(abe && abe->nickname && abe->nickname[0]) savenick = cpystr(abe->nickname); } /* this will close and re-open current addrbook */ (void)adrbk_check_and_fix_all(1, 0, 1); abe = NULL; if(savenick){ abe = adrbk_lookup_by_nick(pab->address_book, savenick, &new_entry_num); fs_give((void **)&savenick); } if(abe){ /* If we found the same nickname, move to it */ dlc_restart.adrbk_num = as.cur; dlc_restart.dlcelnum = new_entry_num; dlc_restart.type = (abe->tag == Single) ? DlcSimple : DlcListHead; if(old_entry_num == new_entry_num) warp_to_dlc(&dlc_restart, old_global_row); else warp_to_dlc(&dlc_restart, 0L); } else warp_to_top_of_abook(as.cur); if(!abe || old_entry_num != new_entry_num){ /* put current entry in middle of screen */ as.top_ent = first_line(0L - (long)as.l_p_page/2L); as.cur_row = 0L - as.top_ent; } } return(current_resynced); } /* * Erase all the check marks. */ void erase_checks(void) { int i; PerAddrBook *pab; for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->address_book && pab->address_book->checks) exp_free(pab->address_book->checks); init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i); } } /* * Erase all the selections. */ void erase_selections(void) { int i; PerAddrBook *pab; for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; if(pab->address_book && pab->address_book->selects) exp_free(pab->address_book->selects); } for(i = 0; i < as.n_addrbk; i++){ pab = &as.adrbks[i]; init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i); } as.selections = 0; } /* * return values of search_in_one_line are or'd combination of these */ #define MATCH_NICK 0x1 /* match in field 0 */ #define MATCH_FULL 0x2 /* match in field 1 */ #define MATCH_ADDR 0x4 /* match in field 2 */ #define MATCH_FCC 0x8 /* match in fcc field */ #define MATCH_COMMENT 0x10 /* match in comment field */ #define MATCH_BIGFIELD 0x20 /* match in one of the fields that crosses the whole screen, like a Title field */ #define MATCH_LISTMEM 0x40 /* match list member */ /* * Prompt user for search string and call find_in_book. * * Args: cur_line -- The current line position (in global display list) * of cursor * command_line -- The screen line to prompt on * new_line -- Return value: new line position * wrapped -- Wrapped to beginning of display, tell user * warped -- Warped to a new location in the addrbook * * Result: The new line number is set if the search is successful. * Returns 0 if found, -1 if not, -2 if cancelled. * */ int search_book(long int cur_line, int command_line, long int *new_line, int *wrapped, int *warped) { int i=0, find_result, rc, flags, ku; static HISTORY_S *history = NULL; char search_string[MAX_SEARCH + 1]; char prompt[MAX_SEARCH + 50], nsearch_string[MAX_SEARCH+1], *p; HelpType help; ESCKEY_S ekey[6]; PerAddrBook *pab; long nl; dprint((7, "- search_book -\n")); init_hist(&history, HISTSIZE); search_string[0] = '\0'; if((p = get_prev_hist(history, "", 0, NULL)) != NULL){ strncpy(search_string, p, sizeof(search_string)); search_string[sizeof(search_string)-1] = '\0'; } snprintf(prompt, sizeof(prompt), _("Word to search for [%.*s]: "), MAX_SEARCH, search_string); prompt[sizeof(prompt)-1] = '\0'; help = NO_HELP; nsearch_string[0] = '\0'; ekey[i].ch = 0; ekey[i].rval = 0; ekey[i].name = ""; ekey[i++].label = ""; ekey[i].ch = ctrl('Y'); ekey[i].rval = 10; ekey[i].name = "^Y"; /* TRANSLATORS: User is searching in address book. One of the options is to search for the First Address */ ekey[i++].label = _("First Adr"); ekey[i].ch = ctrl('V'); ekey[i].rval = 11; ekey[i].name = "^V"; /* TRANSLATORS: Last Address */ ekey[i++].label = _("Last Adr"); ekey[i].ch = KEY_UP; ekey[i].rval = 30; ekey[i].name = ""; ku = i; ekey[i++].label = ""; ekey[i].ch = KEY_DOWN; ekey[i].rval = 31; ekey[i].name = ""; ekey[i++].label = ""; ekey[i].ch = -1; flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE; while(1){ /* * 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){ 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 = ""; } rc = optionally_enter(nsearch_string, command_line, 0, sizeof(nsearch_string), prompt, ekey, help, &flags); if(rc == 3){ help = help == NO_HELP ? h_oe_searchab : NO_HELP; continue; } else if(rc == 10){ *warped = 1; warp_to_beginning(); /* go to top of addrbooks */ if((nl=first_selectable_line(0L)) != NO_LINE){ *new_line = nl; q_status_message(SM_INFO, 0, 2, _("Searched to first entry")); return 0; } else{ q_status_message(SM_INFO, 0, 2, _("No entries")); return -1; } } else if(rc == 11){ *warped = 1; warp_to_end(); /* go to bottom */ if((nl=first_selectable_line(0L)) != NO_LINE){ *new_line = nl; q_status_message(SM_INFO, 0, 2, _("Searched to last entry")); return 0; } else{ q_status_message(SM_INFO, 0, 2, _("No entries")); return -1; } } else if(rc == 30){ if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){ strncpy(nsearch_string, p, sizeof(nsearch_string)); nsearch_string[sizeof(nsearch_string)-1] = '\0'; } else Writechar(BELL, 0); continue; } else if(rc == 31){ if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){ strncpy(nsearch_string, p, sizeof(nsearch_string)); nsearch_string[sizeof(nsearch_string)-1] = '\0'; } else Writechar(BELL, 0); continue; } if(rc != 4){ /* 4 is redraw */ save_hist(history, nsearch_string, 0, NULL); break; } } if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0')) return -2; if(nsearch_string[0] != '\0'){ strncpy(search_string, nsearch_string, sizeof(search_string)-1); search_string[sizeof(search_string)-1] = '\0'; } find_result = find_in_book(cur_line, search_string, new_line, wrapped); if(*wrapped == 1) *warped = 1; if(find_result){ int also = 0, notdisplayed = 0; pab = &as.adrbks[adrbk_num_from_lineno(*new_line)]; if(find_result & MATCH_NICK){ if(pab->nick_is_displayed) also++; else notdisplayed++; } if(find_result & MATCH_FULL){ if(pab->full_is_displayed) also++; else notdisplayed++; } if(find_result & MATCH_ADDR){ if(pab->addr_is_displayed) also++; else notdisplayed++; } if(find_result & MATCH_FCC){ if(pab->fcc_is_displayed) also++; else notdisplayed++; } if(find_result & MATCH_COMMENT){ if(pab->comment_is_displayed) also++; else notdisplayed++; } if(find_result & MATCH_LISTMEM){ AddrScrn_Disp *dl; dl = dlist(*new_line); if(F_OFF(F_EXPANDED_DISTLISTS,ps_global) && !exp_is_expanded(pab->address_book->exp, (a_c_arg_t)dl->elnum)) notdisplayed++; } if(notdisplayed > 1 && *wrapped == 0){ if(also) /* TRANSLATORS: These "matched" messages are advisory messages explaining how a search command matched an entry */ q_status_message1(SM_ORDER,0,4, _("Also matched string in %s other fields"), comatose(notdisplayed)); else q_status_message1(SM_ORDER,0,4, _("Matched string in %s fields"), comatose(notdisplayed)); } else if(notdisplayed == 1 && *wrapped == 0){ if(also){ if(find_result & MATCH_NICK && !pab->nick_is_displayed) q_status_message(SM_ORDER,0,4, _("Also matched string in Nickname field")); else if(find_result & MATCH_FULL && !pab->full_is_displayed) q_status_message(SM_ORDER,0,4, _("Also matched string in Fullname field")); else if(find_result & MATCH_ADDR && !pab->addr_is_displayed) q_status_message(SM_ORDER,0,4, _("Also matched string in Address field")); else if(find_result & MATCH_FCC && !pab->fcc_is_displayed) q_status_message(SM_ORDER,0,4, _("Also matched string in Fcc field")); else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed) q_status_message(SM_ORDER,0,4, _("Also matched string in Comment field")); else if(find_result & MATCH_LISTMEM) q_status_message(SM_ORDER,0,4, _("Also matched string in list member address")); else q_status_message(SM_ORDER,0,4, _("Also matched string in ?")); } else{ if(find_result & MATCH_NICK && !pab->nick_is_displayed) q_status_message(SM_ORDER,0,4, _("Matched string in Nickname field")); else if(find_result & MATCH_FULL && !pab->full_is_displayed) q_status_message(SM_ORDER,0,4, _("Matched string in Fullname field")); else if(find_result & MATCH_ADDR && !pab->addr_is_displayed) q_status_message(SM_ORDER,0,4, _("Matched string in Address field")); else if(find_result & MATCH_FCC && !pab->fcc_is_displayed) q_status_message(SM_ORDER,0,4, _("Matched string in Fcc field")); else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed) q_status_message(SM_ORDER,0,4, _("Matched string in Comment field")); else if(find_result & MATCH_LISTMEM) q_status_message(SM_ORDER,0,4, _("Matched string in list member address")); else q_status_message(SM_ORDER,0,4, _("Matched string in ?")); } } /* be sure to be on a selectable field */ if(!line_is_selectable(*new_line)) if((nl=first_selectable_line(*new_line+1)) != NO_LINE) *new_line = nl; } return(find_result ? 0 : -1); } /* * Search the display list for the given string. * * Args: cur_line -- The current line position (in global display list) * of cursor * string -- String to search for * new_line -- Return value: new line position * wrapped -- Wrapped to beginning of display during search * * Result: The new line number is set if the search is successful. * Returns 0 -- string not found * Otherwise, a bitmask of which fields the string was found in. */ int find_in_book(long int cur_line, char *string, long int *new_line, int *wrapped) { register AddrScrn_Disp *dl; long nl, nl_save; int fields; AdrBk_Entry *abe; char *listaddr = NULL; DL_CACHE_S *dlc, dlc_save; /* a local copy */ dprint((9, "- find_in_book -\n")); /* * Save info to allow us to get back to where we were if we can't find * the string. Also used to stop our search if we wrap back to the * start and search forward. */ nl_save = cur_line; dlc = get_dlc(nl_save); dlc_save = *dlc; *wrapped = 0; nl = cur_line + 1L; /* start with next line and search to the end of the disp_list */ dl = dlist(nl); while(dl->type != End){ if(dl->type == Simple || dl->type == ListHead || dl->type == ListEnt || dl->type == ListClickHere){ abe = ae(nl); if(dl->type == ListEnt) listaddr = listmem(nl); } else abe = (AdrBk_Entry *)NULL; if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0) goto found; dl = dlist(++nl); } /* * Wrap back to the start of the addressbook and search forward * from there. */ warp_to_beginning(); /* go to top of addrbooks */ nl = 0L; /* line number is always 0 after warp_to_beginning */ *wrapped = 1; dlc = get_dlc(nl); while(!matching_dlcs(&dlc_save, dlc) && dlc->type != DlcEnd){ fill_in_dl_field(dlc); dl = &dlc->dl; if(dl->type == Simple || dl->type == ListHead || dl->type == ListEnt || dl->type == ListClickHere){ abe = ae(nl); if(dl->type == ListEnt) listaddr = listmem(nl); } else abe = (AdrBk_Entry *)NULL; if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0) goto found; dlc = get_dlc(++nl); } /* see if it is in the current line */ fill_in_dl_field(dlc); dl = &dlc->dl; if(dl->type == Simple || dl->type == ListHead || dl->type == ListEnt || dl->type == ListClickHere){ abe = ae(nl); if(dl->type == ListEnt) listaddr = listmem(nl); } else abe = (AdrBk_Entry *)NULL; fields = search_in_one_line(dl, abe, listaddr, string); if(dl->usst && (dl->type == Text || dl->type == Title || dl->type == TitleCmb)) fs_give((void **)&dl->usst); /* jump cache back to where we started */ *wrapped = 0; warp_to_dlc(&dlc_save, nl_save); if(fields){ *new_line = nl_save; /* because it was in current line */ *wrapped = 2; } nl = *new_line; found: *new_line = nl; return(fields); } /* * Look in line dl for string. * * Args: dl -- the display list for this line * abe -- AdrBk_Entry if it is an address type * listaddr -- list member if it is of type ListEnt * string -- look for this string * * Result: 0 -- string not found * Otherwise, a bitmask of which fields the string was found in. * MATCH_NICK 0x1 * MATCH_FULL 0x2 * MATCH_ADDR 0x4 * MATCH_FCC 0x8 * MATCH_COMMENT 0x10 * MATCH_BIGFIELD 0x20 * MATCH_LISTMEM 0x40 */ int search_in_one_line(AddrScrn_Disp *dl, AdrBk_Entry *abe, char *listaddr, char *string) { register int c; int ret_val = 0; char **lm; for(c = 0; c < 5; c++){ switch(c){ case 0: switch(dl->type){ case Simple: case ListHead: if(srchstr(abe->nickname, string)) ret_val |= MATCH_NICK; break; case Text: case Title: case TitleCmb: case AskServer: if(srchstr(dl->usst, string)) ret_val |= MATCH_BIGFIELD; default: break; } break; case 1: switch(dl->type){ case Simple: case ListHead: if(abe && srchstr( (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, abe->fullname), string)) ret_val |= MATCH_FULL; default: break; } break; case 2: switch(dl->type){ case Simple: if(srchstr((abe && abe->tag == Single) ? abe->addr.addr : NULL, string)) ret_val |= MATCH_ADDR; break; case ListEnt: if(srchstr( (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, listaddr), string)) ret_val |= MATCH_LISTMEM; break; case ListClickHere: if(abe) for(lm = abe->addr.list; !(ret_val & MATCH_LISTMEM) && *lm; lm++) if(srchstr(*lm, string)) ret_val |= MATCH_LISTMEM; break; case Empty: case ListEmpty: if(srchstr(EMPTY, string)) ret_val |= MATCH_BIGFIELD; break; case AddFirstPers: if(srchstr(ADD_PERSONAL, string)) ret_val |= MATCH_BIGFIELD; break; case AddFirstGlob: if(srchstr(ADD_GLOBAL, string)) ret_val |= MATCH_BIGFIELD; break; case NoAbooks: if(srchstr(NOABOOKS, string)) ret_val |= MATCH_BIGFIELD; break; default: break; } break; case 3: /* fcc */ switch(dl->type){ case Simple: case ListHead: if(abe && srchstr(abe->fcc, string)) ret_val |= MATCH_FCC; default: break; } break; case 4: /* comment */ switch(dl->type){ case Simple: case ListHead: if(abe){ size_t n, len; unsigned char *p, *tmp = NULL; if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){ len = n+1; p = tmp = (unsigned char *)fs_get(len * sizeof(char)); } else{ len = SIZEOF_20KBUF; p = (unsigned char *)tmp_20k_buf; } if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra), string)) ret_val |= MATCH_COMMENT; if(tmp) fs_give((void **)&tmp); } default: break; } break; } } return(ret_val); } /* * These chars in nicknames will mess up parsing. * * Returns 0 if ok, 1 if not. * Returns an allocated error message on error. */ int nickname_check(char *nickname, char **error) { register char *t; char buf[100]; if((t = strindex(nickname, SPACE)) || (t = strindex(nickname, ',')) || (t = strindex(nickname, '"')) || (t = strindex(nickname, ';')) || (t = strindex(nickname, ':')) || (t = strindex(nickname, '@')) || (t = strindex(nickname, '(')) || (t = strindex(nickname, ')')) || (t = strindex(nickname, '\\')) || (t = strindex(nickname, '[')) || (t = strindex(nickname, ']')) || (t = strindex(nickname, '<')) || (t = strindex(nickname, '>'))){ char s[4]; s[0] = '"'; s[1] = *t; s[2] = '"'; s[3] = '\0'; if(error){ /* * TRANSLATORS: this is telling the user that one of the characters * they have included in a nickname will not work. It will say something like * Blank spaces not allowed in nicknames or * Commas not... * etc. */ snprintf(buf, sizeof(buf), _("%s not allowed in nicknames"), *t == SPACE ? _("Blank spaces") : *t == ',' ? _("Commas") : *t == '"' ? _("Quotes") : s); buf[sizeof(buf)-1] = '\0'; *error = cpystr(buf); } return 1; } return 0; } char * abook_select_screen(struct pine *ps) { CONF_S *ctmp = NULL, *first_line = NULL; OPT_SCREEN_S screen; int adrbknum; char *helptitle; HelpType help; PerAddrBook *pab; char *abook = NULL; helptitle = _("HELP FOR SELECTING AN ADDRESS BOOK"); help = h_role_abook_select; init_ab_if_needed(); for(adrbknum = 0; adrbknum < as.n_addrbk; adrbknum++){ new_confline(&ctmp); if(!first_line) first_line = ctmp; pab = &as.adrbks[adrbknum]; ctmp->value = cpystr((pab && pab->abnick) ? pab->abnick : (pab && pab->filename) ? pab->filename : "?"); ctmp->d.b.selected = &abook; ctmp->d.b.abookname = ctmp->value; ctmp->keymenu = &abook_select_km; ctmp->help = help; ctmp->help_title = helptitle; ctmp->tool = abook_select_tool; ctmp->flags = CF_STARTITEM; ctmp->valoffset = 4; } if(first_line){ memset(&screen, 0, sizeof(screen)); (void) conf_scroll_screen(ps, &screen, first_line, /* TRANSLATORS: Print something1 using something2. abooks is something1 */ _("SELECT ADDRESS BOOK"), _("abooks"), 0, NULL); } else q_status_message(SM_ORDER|SM_DING, 3, 3, _("No address books defined!")); ps->mangled_screen = 1; return(abook); } int abook_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags) { int retval; switch(cmd){ case MC_CHOICE : *((*cl)->d.b.selected) = cpystr((*cl)->d.b.abookname); retval = simple_exit_cmd(flags); break; case MC_EXIT : retval = simple_exit_cmd(flags); break; default: retval = -1; break; } if(retval > 0) ps->mangled_body = 1; return(retval); } /* * This isn't actually just a nickname completion anymore. The user types * in a prefix of either a nickname, or a full address, or the addr@mailbox * part of an address and we look in all of the address books for matches * like that. We return the longest unambiguous match in answer. * * Args prefix -- The part of the "nickname" that has been typed so far * answer -- The answer is returned here. * tabtab -- If the answer returned from adrbk_list_of_completions is * ambiguous and tabtab is set, then offer up a * selector screen for all the possible answers. * flags -- ANC_AFTERCOMMA -- This means that the passed in * prefix may be a list of comma * separated addresses and we're only * completing the last one. * * Returns 0 -- no matches at all * 1 -- more than one nickname (or address) begins with * the answer being returned * 2 -- the returned answer is a complete answer, either a * nickname or a complete address, and there are * no longer matches for the prefix * * Allocated answer is returned in answer argument. * Caller needs to free the answer. */ int abook_nickname_complete(char *prefix, char **answer, int tabtab, unsigned flags) { int ambiguity; COMPLETE_S *completions, *cp; char *saved_beginning = NULL; char *potential_answer = NULL; wp_exit = wp_nobail = 0; /* there shouldn't be a case where answer is NULL */ if(answer) *answer = NULL; /* * If we need to handle case where no prefix is passed in, * figure that out when we need it. */ if(!(prefix && prefix[0])) return(0); if(flags & ANC_AFTERCOMMA){ char *lastnick; /* * Find last comma, save the part before that, operate * only on the last address. */ if((lastnick = strrchr(prefix ? prefix : "", ',')) != NULL){ lastnick++; while(!(*lastnick & 0x80) && isspace((unsigned char) (*lastnick))) lastnick++; saved_beginning = cpystr(prefix); saved_beginning[lastnick-prefix] = '\0'; prefix = lastnick; } } if(!(prefix && prefix[0])) return(0); completions = adrbk_list_of_completions(prefix, ps_global->cur_uid_stream, ps_global->cur_uid, ALC_INCLUDE_ADDRS | ((strlen(prefix) >= 3) ? ALC_INCLUDE_LDAP : 0)); if(!completions) ambiguity = 0; else if(completions && completions->next) ambiguity = 1; else ambiguity = 2; if(ambiguity == 2){ if(completions->full_address && completions->full_address[0]) potential_answer = cpystr(completions->full_address); else if(completions->nickname && completions->nickname[0]) potential_answer = cpystr(completions->nickname); else if(completions->addr && completions->addr[0]) potential_answer = cpystr(completions->addr); else potential_answer = cpystr(prefix); } /* answer is ambiguous and caller wants a choose list in that case */ else if(ambiguity == 1 && tabtab){ potential_answer = choose_an_address_with_this_prefix(completions); if(potential_answer) ambiguity = 2; else{ ambiguity = 1; potential_answer = cpystr(prefix); } } else if(ambiguity == 1){ int k; char cand1_kth_char, cand2_kth_char; char unambig[1000]; /* find the longest unambiguous prefix */ strncpy(unambig, prefix, sizeof(unambig)); unambig[sizeof(unambig)-1] = '\0'; k = strlen(unambig); /* * First verify that they all match through prefix. LDAP sometimes gives * weird, inexplicable answers that don't seem to match at all. */ for(cp = completions; cp; cp = cp->next) if(!( /* not a match */ /* no NICK bit OR nickname matches prefix */ (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && !struncmp(unambig, cp->nickname, k))) /* AND no ADDR bit OR addr matches prefix */ && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && !struncmp(unambig, cp->addr, k))) /* AND neither FULL bit is set OR one of the two fulls matches prefix */ && (!(cp->matches_bitmap & (ALC_FULL | ALC_REVFULL)) || ((cp->matches_bitmap & ALC_FULL && cp->full_address && strlen(cp->full_address) >= k && !struncmp(unambig, cp->full_address, k)) || (cp->matches_bitmap & ALC_REVFULL && cp->rev_fullname && strlen(cp->rev_fullname) >= k && !struncmp(unambig, cp->rev_fullname, k)))) )) break; /* if cp that means there was not a universal match up through prefix, stop */ if(!cp) do{ cand1_kth_char = cand2_kth_char = '\0'; if(completions->matches_bitmap & ALC_NICK && completions->nickname && strlen(completions->nickname) >= k) cand1_kth_char = completions->nickname[k]; else if(completions->matches_bitmap & ALC_ADDR && completions->addr && strlen(completions->addr) >= k) cand1_kth_char = completions->addr[k]; else{ if(completions->matches_bitmap & ALC_FULL && completions->full_address && strlen(completions->full_address) >= k) cand1_kth_char = completions->full_address[k]; if(completions->matches_bitmap & ALC_REVFULL && completions->rev_fullname && strlen(completions->rev_fullname) >= k) cand2_kth_char = completions->rev_fullname[k]; } /* * You'll want a wide screen to read this. There are two possible * candidate chars for the next position. One or the other of them * has to match in all of the possible completions. We consider it * a match if either of the fullname completions for this entry is * a match. That may not match what the user expects but it may. */ for(cp = completions; cp; cp = cp->next){ if(!( /* candidate 1 is not a match */ /* candidate 1 is defined */ cand1_kth_char && cand1_kth_char != ',' /* AND no NICK bit OR nickname char is a match */ && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand1_kth_char)) /* AND no ADDR bit OR addr char is a match */ && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand1_kth_char)) /* AND neither FULL bit is set OR one of the two full chars is a match */ && (!(cp->matches_bitmap & (ALC_FULL | ALC_REVFULL)) || ((cp->matches_bitmap & ALC_FULL && cp->full_address && strlen(cp->full_address) >= k && cp->full_address[k] == cand1_kth_char) || (cp->matches_bitmap & ALC_REVFULL && cp->rev_fullname && strlen(cp->rev_fullname) >= k && cp->rev_fullname[k] == cand1_kth_char))) )) cand1_kth_char = '\0'; /* mark that it isn't a match */ if(!cand1_kth_char && !( /* cand1 is not a match AND cand2 is not a match */ /* candidate 2 is defined */ cand2_kth_char && cand2_kth_char != ',' /* AND no NICK bit OR nickname char is a match */ && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand2_kth_char)) /* AND no ADDR bit OR addr char is a match */ && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand2_kth_char)) /* AND neither FULL bit is set OR one of the two full chars is a match */ && (!(cp->matches_bitmap & (ALC_FULL | ALC_REVFULL)) || ((cp->matches_bitmap & ALC_FULL && cp->full_address && strlen(cp->full_address) >= k && cp->full_address[k] == cand2_kth_char) || (cp->matches_bitmap & ALC_REVFULL && cp->rev_fullname && strlen(cp->rev_fullname) >= k && cp->rev_fullname[k] == cand2_kth_char))) )) cand2_kth_char = '\0'; /* mark that it isn't a match */ if(!cand1_kth_char && !cand2_kth_char) break; /* no match so break */ } if(!cp) /* they all matched */ unambig[k++] = cand1_kth_char ? cand1_kth_char : cand2_kth_char; }while(!cp && k < sizeof(unambig)-1); unambig[k] = '\0'; unambig[sizeof(unambig)-1] = '\0'; /* don't return answer with trailing space */ while(--k >= 0 && isspace((unsigned char) unambig[k])) unambig[k] = '\0'; potential_answer = cpystr(unambig); } if(completions) free_complete_s(&completions); if(answer && ambiguity != 0){ if(potential_answer){ if(saved_beginning){ size_t l1, l2; l1 = strlen(saved_beginning); l2 = strlen(potential_answer); *answer = (char *) fs_get((l1+l2+1) * sizeof(char)); strncpy(*answer, saved_beginning, l1+l2); strncpy(*answer+l1, potential_answer, l2); (*answer)[l1+l2] = '\0'; } else{ *answer = potential_answer; potential_answer = NULL; } } else{ /* this can't happen */ ambiguity = 0; } } if(saved_beginning) fs_give((void **) &saved_beginning); if(potential_answer) fs_give((void **) &potential_answer); return(ambiguity); } /* * Returns an allocated nickname choice from user that begins with * prefix. */ char * choose_an_address_with_this_prefix(COMPLETE_S *completions) { char buf[1000]; char *chosen_address = NULL; char **lp, **da, **possible_addrs = NULL, **display_addrs = NULL; COMPLETE_S *cp; size_t cnt = 0; int show_nick, show_revfull; /* * Count how many and allocate an array for choose_item_from_list(). */ for(cnt = 0, cp = completions; cp; cp = cp->next) cnt++; /* * Copy completions into an array. */ if(cnt > 0){ lp = possible_addrs = (char **) fs_get((cnt+1) * sizeof(*possible_addrs)); memset(possible_addrs, 0, (cnt+1) * sizeof(*possible_addrs)); da = display_addrs = (char **) fs_get((cnt+1) * sizeof(*display_addrs)); memset(display_addrs, 0, (cnt+1) * sizeof(*display_addrs)); for(cp = completions; cp; cp = cp->next){ show_nick = (cp->matches_bitmap & ALC_NICK) && cp->nickname && cp->nickname[0]; show_revfull = 0; if(!show_nick && !(cp->matches_bitmap & (ALC_NICK | ALC_ADDR | ALC_FULL)) && (cp->matches_bitmap & ALC_REVFULL) && cp->rev_fullname && cp->rev_fullname[0]) show_revfull = 1; snprintf(buf, sizeof(buf), "%s%s%s%s%s", cp->full_address ? cp->full_address : "?", (show_nick || show_revfull) ? " (" : "", show_nick ? cp->nickname : "", show_revfull ? cp->rev_fullname : "", (show_nick || show_revfull) ? ")" : ""); *da++ = cpystr(buf); *lp++ = cpystr(cp->full_address ? cp->full_address : "?"); } } if(possible_addrs){ chosen_address = choose_item_from_list(possible_addrs, display_addrs, _("SELECT AN ADDRESS"), _("addresses"), h_select_address_screen, _("HELP FOR SELECTING AN ADDRESS"), NULL); free_list_array(&possible_addrs); } if(display_addrs) free_list_array(&display_addrs); return(chosen_address); } #ifdef _WINDOWS /* * addr_scroll_up - adjust the global screen state struct such that pine's * window on the data is shifted DOWN (i.e., the data's * scrolled up). */ int addr_scroll_up(count) long count; { int next; if(count < 0) return(addr_scroll_down(-count)); else if(count){ long i; i=count; as.cur_row += as.top_ent; while(i && as.top_ent + 1 < as.last_ent){ if(line_is_selectable(as.top_ent)){ if(next_selectable_line(as.top_ent,&next)){ as.cur_row = next; i--; as.top_ent++; } else i = 0; } else { i--; as.top_ent++; } } as.cur_row = as.cur_row - as.top_ent; /* must always be positive */ as.old_cur_row = as.cur_row; } return(1); } /* * addr_scroll_down - adjust the global screen state struct such that pine's * window on the data is shifted UP (i.e., the data's * scrolled down). */ int addr_scroll_down(count) long count; { if(count < 0) return(addr_scroll_up(-count)); else if(count){ long i; for(i = count; i && as.top_ent; i--, as.top_ent--) as.cur_row++; while (as.cur_row >= as.l_p_page){ prev_selectable_line(as.cur_row+as.top_ent, &as.cur_row); as.cur_row = as.cur_row - as.top_ent; } as.old_cur_row = as.cur_row; } return(1); } /* * addr_scroll_to_pos - scroll the address book data in pine's window such * tthat the given "line" is at the top of the page. */ int addr_scroll_to_pos(line) long line; { return(addr_scroll_up(line - as.top_ent)); } /*---------------------------------------------------------------------- MSWin scroll callback. Called during scroll message processing. Args: cmd - what type of scroll operation. scroll_pos - parameter for operation. used as position for SCROLL_TO operation. Returns: TRUE - did the scroll operation. FALSE - was not able to do the scroll operation. ----*/ int addr_scroll_callback (cmd, scroll_pos) int cmd; long scroll_pos; { int paint = TRUE; switch (cmd) { case MSWIN_KEY_SCROLLUPLINE: paint = addr_scroll_down (scroll_pos); break; case MSWIN_KEY_SCROLLDOWNLINE: paint = addr_scroll_up (scroll_pos); break; case MSWIN_KEY_SCROLLUPPAGE: paint = addr_scroll_down (as.l_p_page); break; case MSWIN_KEY_SCROLLDOWNPAGE: paint = addr_scroll_up (as.l_p_page); break; case MSWIN_KEY_SCROLLTO: paint = addr_scroll_to_pos (scroll_pos); break; } if(paint) display_book(0, as.cur_row, -1, 1, (Pos *)NULL); return(paint); } char * pcpine_help_addrbook(title) char *title; { /* * Title is size 256. Fix this to pass the titlelen. */ if(title) strncpy(title, (as.config) ? _("Alpine CONFIGURING ADDRESS BOOKS Help") : _("Alpine ADDRESS_BOOK Help"), 256); return(pcpine_help(gAbookHelp)); } #endif /* _WINDOWS */