* Add new hidden feature "Enable Delete Before Writing" that makes Alpine
[alpine.git] / alpine / addrbook.c
blob2860e8857a4056da4fe3d12b5cdff9da6f8feb9c
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: addrbook.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2006-2009 University of Washington
8 * Copyright 2013-2021 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 addrbook.c
21 display, browse and edit the address book.
22 ====*/
25 #include "headers.h"
26 #include "addrbook.h"
27 #include "adrbkcmd.h"
28 #include "mailindx.h"
29 #include "mailcmd.h"
30 #include "status.h"
31 #include "confscroll.h"
32 #include "keymenu.h"
33 #include "radio.h"
34 #include "titlebar.h"
35 #include "help.h"
36 #include "folder.h"
37 #include "signal.h"
38 #include "alpine.h"
39 #include "../pith/adrbklib.h"
40 #include "../pith/abdlc.h"
41 #include "../pith/addrbook.h"
42 #include "../pith/ablookup.h"
43 #include "../pith/addrstring.h"
44 #include "../pith/bldaddr.h"
45 #include "../pith/state.h"
46 #include "../pith/bitmap.h"
47 #include "../pith/newmail.h"
48 #include "../pith/remote.h"
49 #include "../pith/util.h"
50 #include "../pith/thread.h"
51 #include "../pith/hist.h"
52 #include "../pith/list.h"
55 HelpType gAbookHelp = NO_HELP;
58 /* internal prototypes */
59 void end_adrbks(void);
60 int init_disp_form_prefix(PerAddrBook *, int *);
61 void display_book(int, int, int, int, Pos *);
62 void paint_line(int, long, int, Pos *);
63 void redraw_addr_screen(void);
64 char *addr_book(AddrBookArg, char *, char **);
65 void ab_zoom(EXPANDED_S *, int *);
66 void ab_unzoom(int *);
67 void empty_warning(long);
68 void clickable_warning(long);
69 void no_tabs_warning(void);
70 int ab_apply_cmd(struct pine *, AdrBk *, long, int);
71 void ab_select(struct pine *, AdrBk *, long, int, int *);
72 int ab_select_type(AdrBk *, int);
73 int ab_select_text(AdrBk *, int);
74 int match_check(AdrBk_Entry *, int, char *);
75 void ab_goto_folder(int);
76 long ab_whereis(int *, int);
77 int any_addrs_avail(long);
78 int entry_is_clickable(long);
79 int entry_is_clickable_title(long);
80 int is_empty(long);
81 int entry_is_listent(long);
82 int entry_is_addkey(long);
83 int entry_is_askserver(long);
84 int add_is_global(long);
85 int next_selectable_line(long, long *);
86 int prev_selectable_line(long, long *);
87 void erase_checks(void);
88 int search_book(long, int, long *, int *, int *);
89 int find_in_book(long, char *, long *, int *);
90 int search_in_one_line(AddrScrn_Disp *, AdrBk_Entry *, char *, char *);
91 int abook_select_tool(struct pine *, int, CONF_S **, unsigned);
92 char *choose_an_address_with_this_prefix(COMPLETE_S *);
93 #ifdef _WINDOWS
94 int addr_scroll_up(long);
95 int addr_scroll_down(long);
96 int addr_scroll_to_pos(long);
97 int addr_scroll_callback(int, long);
98 char *pcpine_help_addrbook(char *);
99 #endif
102 /* TRANSLATORS: This is a placeholder for a list of addresses in address book */
103 #define CLICKHERE _("[ Address List ]")
104 /* TRANSLATORS: This is an empty address list */
105 #define EMPTY _("[ Empty ]")
106 #define ZOOM_EMPTY _("[ No Selected Entries in this Address Book ]")
107 /* TRANSLATORS: Move here means to move the cursor to this line of the screen */
108 #define ADD_PERSONAL _(" [ Move here to add a Personal Address Book ]")
109 /* TRANSLATORS: A global address book is a shared address book */
110 #define ADD_GLOBAL _(" [ Move here to add a Global Address Book ]")
111 /* TRANSLATORS: A heading for an address book distribution list */
112 #define DISTLIST _("DISTRIBUTION LIST:")
113 #define NOABOOKS _("[ No Address Book Configured ]")
114 /* TRANSLATORS: Select here means to move the cursor to this line and then type
115 the Select command. */
116 #define CLICKHERECMB _("[ Select Here to See Expanded List ]")
121 * Returns the index of the current address book.
124 cur_addr_book(void)
126 return(adrbk_num_from_lineno(as.top_ent + as.cur_row));
131 * Returns 1 if current abook is open, else 0.
134 cur_is_open(void)
136 int current, ret = 0;
138 if(as.initialized &&
139 as.n_addrbk > 0 &&
140 (current = cur_addr_book()) >= 0 &&
141 current < as.n_addrbk &&
142 !entry_is_askserver(as.top_ent+as.cur_row))
143 ret = (as.adrbks[current].ostatus == Open);
145 return(ret);
149 void
150 end_adrbks(void)
152 int i;
154 dprint((2, "- end_adrbks -\n"));
156 if(!as.initialized)
157 return;
159 for(i = 0; i < as.n_addrbk; i++)
160 init_abook(&as.adrbks[i], Closed);
162 as.selections = 0;
163 as.checkboxes = 0;
164 ab_nesting_level = 0;
170 * We have to call this to set up the format of the columns. There is a
171 * separate format for each addrbook, so we need to call this for each
172 * addrbook. We call it when the pab's are built. It also depends on
173 * whether or not as.checkboxes is set, so if we go into a Select mode
174 * from the address book maintenance screen we need to re-call this. Since
175 * we can't go back out of ListMode we don't have that problem. Restore_state
176 * has to call it because of the as.checkboxes possibly being different in
177 * the two states.
179 void
180 init_disp_form(PerAddrBook *pab, char **list, int addrbook_num)
182 addrbook_new_disp_form(pab, list, addrbook_num, init_disp_form_prefix);
187 init_disp_form_prefix(PerAddrBook *pab, int *columnp)
189 int rv = 0;
191 if(as.checkboxes){
192 pab->disp_form[(*columnp)].wtype = Fixed;
193 pab->disp_form[(*columnp)].req_width = 3;
194 pab->disp_form[(*columnp)++].type = Checkbox;
196 else if(as.selections){
197 if(F_ON(F_SELECTED_SHOWN_BOLD, ps_global) && StartBold()){
198 EndBold();
199 rv = 1;
201 else{
202 pab->disp_form[(*columnp)].wtype = Fixed;
203 pab->disp_form[(*columnp)].req_width = 1;
204 pab->disp_form[(*columnp)++].type = Selected;
208 return(rv);
213 * save_and_restore - single interface to save_state and restore_state
215 void
216 save_and_restore(int cmd, SAVE_STATE_S *state)
218 switch(cmd){
219 case SAR_SAVE :
220 save_state(state);
221 break;
222 case SAR_RESTORE :
223 restore_state(state);
224 break;
230 * Save the screen state and the Open or Closed status of the addrbooks.
232 void
233 save_state(SAVE_STATE_S *state)
235 int i;
236 DL_CACHE_S *dlc;
238 dprint((9, "- save_state -\n"));
240 /* allocate space for saving the screen structure and save it */
241 state->savep = (AddrScrState *)fs_get(sizeof(AddrScrState));
242 *(state->savep) = as; /* copy the struct */
245 if(as.initialized){
246 /* allocate space for saving the ostatus for each addrbook */
247 state->stp = (OpenStatus *)fs_get(as.n_addrbk * sizeof(OpenStatus));
249 for(i = 0; i < as.n_addrbk; i++)
250 (state->stp)[i] = as.adrbks[i].ostatus;
253 state->dlc_to_warp_to = (DL_CACHE_S *)fs_get(sizeof(DL_CACHE_S));
254 dlc = get_dlc(as.top_ent + as.cur_row);
255 *(state->dlc_to_warp_to) = *dlc; /* copy the struct */
261 * Restore the state.
263 * Side effect: Flushes addrbook entry cache entries so they need to be
264 * re-fetched afterwords. This only applies to entries obtained since
265 * the call to save_state.
266 * Also flushes all dlc cache entries, so dlist calls need to be repeated.
268 void
269 restore_state(SAVE_STATE_S *state)
271 int i;
273 dprint((9, "- restore_state -\n"));
275 as = *(state->savep); /* put back cur_row and all that */
277 if(as.initialized){
278 /* restore addressbook OpenStatus to what it was before */
279 for(i = 0; i < as.n_addrbk; i++){
280 init_disp_form(&as.adrbks[i], ps_global->VAR_ABOOK_FORMATS, i);
281 init_abook(&as.adrbks[i], (state->stp)[i]);
285 * jump cache back to where we were
287 warp_to_dlc(state->dlc_to_warp_to, as.top_ent+as.cur_row);
289 fs_give((void **)&state->dlc_to_warp_to);
290 fs_give((void **)&state->stp);
293 if(state->savep)
294 fs_give((void **)&state->savep);
299 * Returns the addrbook entry for this display row.
301 AdrBk_Entry *
302 ae(long int row)
304 PerAddrBook *pab;
305 LineType type;
306 AddrScrn_Disp *dl;
308 dl = dlist(row);
309 type = dl->type;
310 if(!(type == Simple || type == ListHead ||
311 type == ListEnt || type == ListClickHere))
312 return((AdrBk_Entry *)NULL);
314 pab = &as.adrbks[adrbk_num_from_lineno(row)];
316 return(adrbk_get_ae(pab->address_book, (a_c_arg_t) dl->elnum));
321 * Args: start_disp -- line to start displaying on when redrawing, 0 is
322 * the top_of_screen
323 * cur_line -- current line number (0 is 1st line we display)
324 * old_line -- old line number
325 * redraw -- flag requesting redraw as opposed to update of
326 * current line
327 * start_pos -- return position where highlighted text begins here
329 * Result: lines painted on the screen
331 * It either redraws the screen from line "start_disp" down or
332 * moves the cursor from one field to another.
334 void
335 display_book(int start_disp, int cur_line, int old_line, int redraw, Pos *start_pos)
337 int screen_row, highlight;
338 long global_row;
339 Pos sp;
341 dprint((9,
342 "- display_book() -\n top %d start %d cur_line %d old_line %d redraw %d\n",
343 as.top_ent, start_disp, cur_line, old_line, redraw));
345 if(start_pos){
346 start_pos->row = 0;
347 start_pos->col = 0;
350 if(as.l_p_page <= 0)
351 return;
353 #ifdef _WINDOWS
354 mswin_beginupdate();
355 #endif
356 if(redraw){
357 /*--- Repaint all of the screen or bottom part of screen ---*/
358 global_row = as.top_ent + start_disp;
359 for(screen_row = start_disp;
360 screen_row < as.l_p_page;
361 screen_row++, global_row++){
363 highlight = (screen_row == cur_line);
364 ClearLine(screen_row + HEADER_ROWS(ps_global));
365 paint_line(screen_row + HEADER_ROWS(ps_global), global_row,
366 highlight, &sp);
367 if(start_pos && highlight)
368 *start_pos = sp;
371 else{
373 /*--- Only update current, or move the cursor ---*/
374 if(cur_line != old_line){
376 /*--- Repaint old position to erase "cursor" ---*/
377 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
378 ClearLine(old_line + HEADER_ROWS(ps_global));
379 paint_line(old_line + HEADER_ROWS(ps_global), as.top_ent + old_line,
380 0, &sp);
383 /*--- paint the position with the cursor ---*/
384 if(F_ON(F_ENABLE_DEL_WHEN_WRITING, ps_global))
385 ClearLine(cur_line + HEADER_ROWS(ps_global));
386 paint_line(cur_line + HEADER_ROWS(ps_global), as.top_ent + cur_line,
387 1, &sp);
388 if(start_pos)
389 *start_pos = sp;
392 #ifdef _WINDOWS
393 scroll_setpos(as.top_ent);
394 mswin_endupdate();
395 #endif
396 fflush(stdout);
401 * Paint a line on the screen
403 * Args: line -- Line on screen to paint
404 * global_row -- Row number of line to paint
405 * highlight -- Line should be highlighted
406 * start_pos -- return position where text begins here
408 * Result: Line is painted
410 * The three field widths for the formatting are passed in. There is an
411 * implicit 2 spaces between the fields.
413 * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ...
415 void
416 paint_line(int line, long int global_row, int highlight, Pos *start_pos)
418 int scol, bolden = 0;
419 char *start_hilite_here, *end_hilite_here;
420 AddrScrn_Disp *dl;
421 char lbuf[6*MAX_SCREEN_COLS + 1];
422 register char *p;
424 dprint((10, "- paint_line(%d, %d) -\n", line, highlight));
426 dl = dlist(global_row);
427 start_pos->row = line;
428 start_pos->col = 0; /* default */
430 switch(dl->type){
431 case Beginning:
432 case End:
433 return;
435 default:
436 break;
439 p = get_abook_display_line(global_row, line == HEADER_ROWS(ps_global),
440 highlight ? &start_hilite_here : NULL,
441 highlight ? &end_hilite_here : NULL,
442 &scol, lbuf, sizeof(lbuf));
444 if(as.selections &&
445 as.do_bold &&
446 (dl->type == ListHead || dl->type == Simple)){
447 PerAddrBook *pab;
449 pab = &as.adrbks[adrbk_num_from_lineno(global_row)];
450 if(entry_is_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum)){
451 bolden++;
452 StartBold();
456 if(p && *p){
457 if(highlight){
458 char save_char;
460 MoveCursor(line, 0);
463 * print part before highlight starts
465 if(start_hilite_here != NULL){
466 save_char = *start_hilite_here;
467 *start_hilite_here = '\0';
468 Write_to_screen(p);
469 *start_hilite_here = save_char;
473 * print highlighted part
476 if(end_hilite_here != NULL){
477 save_char = *end_hilite_here;
478 *end_hilite_here = '\0';
481 StartInverse();
482 Write_to_screen(start_hilite_here ? start_hilite_here : p);
483 EndInverse();
486 * print part after highlight ends
488 if(end_hilite_here != NULL){
489 *end_hilite_here = save_char;
490 Write_to_screen(end_hilite_here);
493 else
494 PutLine0(line, 0, p);
497 if(bolden)
498 EndBold();
500 if(scol > 0)
501 start_pos->col = scol;
506 * Assemble a line suitable for displaying on screen
508 * Args: global_row -- Row number of line to assemble.
509 * continuation -- This is the top line of screen display
510 * s_hilite -- Location in the returned line where highlight
511 * should start, if desired
512 * e_hilite -- Location in the returned line where highlight
513 * should end, if desired
514 * retcol -- Return column where text begins here.
515 * lbuf -- Put the output here. Lbuf should be at least
516 * size 6*screen_cols+1.
517 * lbufsize -- Size of lbuf array
519 * Result: Pointer to lbuf is returned.
521 * There is an implicit 2 spaces between the fields.
523 * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ...
525 char *
526 get_abook_display_line(long int global_row, int continuation, char **s_hilite,
527 char **e_hilite, int *retcol, char *lbuf, size_t lbufsize)
529 int fld_col[NFIELDS],
530 fld_width[NFIELDS],
531 screen_width, width,
532 col, special_col = 0,
533 width_consumed,
534 scol = -1,
535 fld;
536 char *string, *writeptr;
537 char special[6*MAX_SCREEN_COLS-1];
538 AddrScrn_Disp *dl;
539 AdrBk_Entry *abe;
540 PerAddrBook *pab;
541 #define LSPACE() (lbufsize - (writeptr - lbuf) - 1)
543 dprint((10, "- get_display_line(%d) -\n", global_row));
545 dl = dlist(global_row);
546 if(retcol)
547 *retcol = 0; /* default */
549 if(s_hilite){
550 *s_hilite = NULL; /* NULL in these means to highlight whole line */
551 *e_hilite = NULL;
554 writeptr = lbuf;
555 memset(writeptr, 0, lbufsize);
556 memset(special, 0, sizeof(special));
558 switch(dl->type){
559 case Beginning:
560 case End:
561 return lbuf;
563 default:
564 break;
567 screen_width = ps_global->ttyo->screen_cols;
569 /* the types in this set span all columns */
570 switch(dl->type){
571 /* center these */
572 case Text:
573 case Title:
574 case TitleCmb:
575 case ClickHereCmb:
576 case Empty:
577 case ZoomEmpty:
578 case NoAbooks:
579 if(dl->type == Empty)
580 string = EMPTY;
581 else if(dl->type == ZoomEmpty)
582 string = ZOOM_EMPTY;
583 else if(dl->type == NoAbooks)
584 string = NOABOOKS;
585 else if(dl->type == ClickHereCmb)
586 string = CLICKHERECMB;
587 else
588 string = dl->usst;
590 /* center it */
591 col = (screen_width - (int) utf8_width(string))/2;
592 col = MAX(col, 0);
593 width_consumed = 0;
595 /* col spaces to start */
596 if(col > 0 && LSPACE() >= col){
597 memset(writeptr, ' ', col);
598 writeptr += col;
599 width_consumed += col;
602 if((width=utf8_width(string)) <= screen_width-col){
603 strncpy(writeptr, string, LSPACE());
604 width_consumed += width;
606 else{
607 utf8_pad_to_width(writeptr, string, LSPACE(), screen_width-col, 1);
608 width_consumed = screen_width;
611 if(s_hilite && *s_hilite == NULL){
612 *s_hilite = writeptr;
613 *e_hilite = writeptr + strlen(writeptr);
616 writeptr += strlen(writeptr);
617 if(width_consumed < screen_width)
618 memset(writeptr, ' ', screen_width-width_consumed);
620 if(retcol)
621 *retcol = col;
623 return lbuf;
625 /* left adjust these */
626 case AddFirstPers:
627 case AddFirstGlob:
628 case AskServer:
629 if(dl->type == AddFirstPers)
630 string = ADD_PERSONAL;
631 else if(dl->type == AddFirstGlob)
632 string = ADD_GLOBAL;
633 else
634 string = dl->usst;
636 /* left adjust it */
637 col = 0;
638 width_consumed = 0;
639 if((width=utf8_width(string)) <= screen_width){
640 strncpy(writeptr, string, LSPACE());
641 width_consumed += width;
643 else{
644 utf8_pad_to_width(writeptr, string, LSPACE(), screen_width, 1);
645 width_consumed = screen_width;
648 if(s_hilite && *s_hilite == NULL){
649 *s_hilite = writeptr;
650 *e_hilite = writeptr + strlen(writeptr);
653 writeptr += strlen(writeptr);
654 if(width_consumed < screen_width)
655 memset(writeptr, ' ', screen_width-width_consumed);
657 if(retcol)
658 *retcol = col;
660 return lbuf;
662 default:
663 break;
666 pab = &as.adrbks[adrbk_num_from_lineno(global_row)];
667 for(fld = 0; fld < NFIELDS; fld++)
668 fld_width[fld] = pab->disp_form[fld].width;
670 fld_col[0] = 0;
671 for(fld = 1; fld < NFIELDS; fld++)
672 fld_col[fld] = MIN(fld_col[fld-1]+fld_width[fld-1]+2, screen_width);
674 width_consumed = 0;
676 /* fill in the fields */
677 for(fld = 0; fld < NFIELDS; fld++){
678 if(fld_width[fld] == 0
679 && !(pab->disp_form[fld].type == Addr
680 && (dl->type == ListClickHere || dl->type == ListEmpty)))
681 continue;
683 switch(pab->disp_form[fld].type){
684 case Notused:
685 break;
687 case Nickname:
688 switch(dl->type){
689 case Simple:
690 case ListHead:
691 abe = ae(global_row);
692 string = (abe && abe->nickname) ? abe->nickname : "";
693 if(scol == -1)
694 scol = fld_col[fld];
696 /* left adjust string in field */
697 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
698 writeptr += strlen(writeptr);
699 width_consumed += fld_width[fld];
700 break;
702 default:
703 break;
706 break;
708 case Fullname:
709 switch(dl->type){
710 case Simple:
711 case ListHead:
712 abe = ae(global_row);
713 string = (abe && abe->fullname) ? abe->fullname : "";
714 if(scol == -1)
715 scol = fld_col[fld];
717 /* left adjust string in field */
718 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
719 writeptr += strlen(writeptr);
720 width_consumed += fld_width[fld];
721 break;
723 case ListEnt:
724 /* continuation line */
725 if(continuation){
726 int width1, width2;
728 width2 = MIN(11, fld_width[fld]);
729 width1 = MAX(fld_width[fld] - width2 - 1, 0);
731 abe = ae(global_row);
732 string = (abe && abe->fullname) ? abe->fullname : "";
734 if(width1){
735 utf8_pad_to_width(writeptr, string, LSPACE(), width1, 1);
736 writeptr += strlen(writeptr);
737 if(LSPACE() > 0)
738 *writeptr++ = ' ';
741 /* TRANSLATORS: continuation line meaning there is more on the next page */
742 if(width2 && LSPACE() >= strlen(_("(continued)"))){
743 strncpy(writeptr, _("(continued)"), width2);
744 lbuf[lbufsize-1] = '\0';
745 writeptr += strlen(writeptr);
748 width_consumed += fld_width[fld];
750 lbuf[lbufsize-1] = '\0';
753 break;
755 default:
756 break;
759 break;
761 case Addr:
762 switch(dl->type){
763 case ListClickHere:
764 case ListEmpty:
765 if(dl->type == ListClickHere)
766 string = CLICKHERE;
767 else
768 string = EMPTY;
770 if((width=utf8_width(string)) <= fld_width[fld]){
772 strncpy(writeptr, string, LSPACE());
774 if(s_hilite && *s_hilite == NULL){
775 *s_hilite = writeptr;
776 *e_hilite = writeptr + strlen(writeptr);
779 writeptr += strlen(writeptr);
780 width_consumed += width;
782 if(scol == -1)
783 scol = fld_col[fld];
785 else{
787 * Place the string in special array and overlay it
788 * onto the right edge of the screen at the end.
790 if(width <= screen_width){
791 strncpy(special, string, sizeof(special));
792 special_col = screen_width - width;
794 else{
795 utf8_pad_to_width(special, string, sizeof(special), screen_width, 1);
796 special_col = 0;
799 special[sizeof(special)-1] = '\0';
802 break;
804 case ListHead:
805 if(scol == -1)
806 scol = fld_col[fld];
808 string = DISTLIST;
809 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
810 writeptr += strlen(writeptr);
811 width_consumed += fld_width[fld];
812 break;
814 case Simple:
815 abe = ae(global_row);
816 #ifdef ENABLE_LDAP
817 if(abe && abe->addr.addr &&
818 !strncmp(abe->addr.addr, QRUN_LDAP, LEN_QRL))
819 string = LDAP_DISP;
820 else
821 #endif
822 string = (abe && abe->tag == Single && abe->addr.addr) ?
823 abe->addr.addr : "";
824 if(scol == -1)
825 scol = fld_col[fld];
827 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
828 writeptr += strlen(writeptr);
829 width_consumed += fld_width[fld];
830 break;
832 case ListEnt:
833 string = listmem(global_row) ? listmem(global_row) : "";
834 if(scol == -1)
835 scol = fld_col[fld];
837 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
839 if(s_hilite && *s_hilite == NULL){
840 *s_hilite = writeptr;
841 *e_hilite = writeptr + strlen(writeptr);
844 writeptr += strlen(writeptr);
845 width_consumed += fld_width[fld];
847 break;
849 default:
850 break;
852 break;
854 case Filecopy:
855 case Comment:
856 switch(dl->type){
857 case Simple:
858 case ListHead:
859 abe = ae(global_row);
860 if(pab->disp_form[fld].type == Filecopy)
861 string = (abe && abe->fcc) ? abe->fcc : "";
862 else
863 string = (abe && abe->extra) ? abe->extra : "";
865 if(scol == -1)
866 scol = fld_col[fld];
868 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
869 writeptr += strlen(writeptr);
870 width_consumed += fld_width[fld];
871 break;
873 default:
874 break;
877 break;
879 case Checkbox:
880 switch(dl->type){
881 case Simple:
882 case ListHead:
883 if(entry_is_checked(pab->address_book->checks,
884 (a_c_arg_t)dl->elnum))
885 string = "[X]";
886 else
887 string = "[ ]";
889 if(scol == -1)
890 scol = fld_col[fld];
892 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
893 writeptr += strlen(writeptr);
894 width_consumed += fld_width[fld];
895 break;
897 default:
898 break;
901 break;
903 case Selected:
904 switch(dl->type){
905 case Simple:
906 case ListHead:
907 if(entry_is_selected(pab->address_book->selects,
908 (a_c_arg_t)dl->elnum))
909 string = "X";
910 else
911 string = " ";
913 if(scol == -1)
914 scol = fld_col[fld];
916 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
917 writeptr += strlen(writeptr);
918 width_consumed += fld_width[fld];
919 break;
921 default:
922 break;
925 break;
927 case WhenNoAddrDisplayed:
928 switch(dl->type){
929 case ListClickHere:
930 case ListEmpty:
931 case ListEnt:
932 if(dl->type == ListClickHere)
933 string = CLICKHERE;
934 else if(dl->type == ListEmpty)
935 string = EMPTY;
936 else
937 string = listmem(global_row) ? listmem(global_row) : "";
939 if((width=utf8_width(string)) <= fld_width[fld])
940 strncpy(special, string, sizeof(special));
941 else
942 utf8_pad_to_width(special, string, sizeof(special), fld_width[fld], 1);
944 special[sizeof(special)-1] = '\0';
945 special_col = screen_width - fld_width[fld];
946 special_col = MAX(0, special_col);
947 break;
949 default:
950 break;
953 break;
955 default:
956 break;
959 if(fld < NFIELDS-1)
960 while(width_consumed < fld_col[fld+1] && LSPACE() > 0){
961 *writeptr++ = ' ';
962 width_consumed++;
967 * Right adjust the special string in lbuf, writing over
968 * anything in our way.
970 if(special[0]){
971 unsigned got_width;
973 writeptr = utf8_count_forw_width(lbuf, special_col, &got_width);
975 strncpy(writeptr, special, LSPACE());
977 if(scol == -1)
978 scol = got_width;
980 if(s_hilite && *s_hilite == NULL){
981 *s_hilite = writeptr;
982 *e_hilite = writeptr + strlen(writeptr);
985 writeptr += strlen(writeptr);
987 width_consumed = (int) utf8_width(lbuf);
988 while(width_consumed++ < screen_width && LSPACE() > 0)
989 *writeptr++ = ' ';
991 if(LSPACE() > 0)
992 *writeptr = '\0';
995 if(scol > 0 && retcol)
996 *retcol = scol;
998 lbuf[lbufsize-1] = '\0';
999 return lbuf;
1004 * Set field widths for the columns of the display. The idea is to
1005 * try to come up with something that works pretty well. Getting it just
1006 * right isn't important.
1008 * Col1 and col2 are arrays which contain some widths useful for
1009 * formatting the screen. The 0th element is the max
1010 * width in that column. The 1st element is the max of the third largest
1011 * width in each addressbook (yup, strange).
1013 * The info above applies to the default case (AllAuto). The newer methods
1014 * where the user specifies the format is the else part of the big if and
1015 * is quite a bit different. It's all sort of ad hoc when we're asked
1016 * to calculate one of the fields for the user.
1018 * Returns non-zero if the widths changed since the last time called.
1021 calculate_field_widths(void)
1023 int space_left, i, j, screen_width;
1024 int ret = 0;
1025 PerAddrBook *pab;
1026 WIDTH_INFO_S *widths;
1027 int max_nick, max_full, max_addr, third_full, third_addr, third_fcc;
1028 int col1[5], col2[5];
1029 int nick = -1, full = -1, addr = -1;
1030 #define SEP 2 /* space between columns */
1032 dprint((9, "- calculate_field_widths -\n"));
1034 screen_width = ps_global->ttyo->screen_cols;
1036 /* calculate widths for each addrbook independently */
1037 for(j = 0; j < as.n_addrbk; j++){
1038 pab = &as.adrbks[j];
1040 max_nick = 0;
1041 max_full = 2;
1042 max_addr = 2;
1043 third_full = 2;
1044 third_addr = 2;
1045 third_fcc = 2;
1047 if(pab->address_book){
1048 widths = &pab->address_book->widths;
1049 max_nick = MIN(MAX(max_nick, widths->max_nickname_width), 25);
1050 max_full = MAX(max_full, widths->max_fullname_width);
1051 max_addr = MAX(max_addr, widths->max_addrfield_width);
1052 third_full = MAX(third_full, widths->third_biggest_fullname_width);
1053 if(third_full == 2)
1054 third_full = MAX(third_full, 2*max_full/3);
1056 third_addr = MAX(third_addr, widths->third_biggest_addrfield_width);
1057 if(third_addr == 2)
1058 third_addr = MAX(third_addr, 2*max_addr/3);
1060 third_fcc = MAX(third_fcc, widths->third_biggest_fccfield_width);
1061 if(third_fcc == 2)
1062 third_fcc = MAX(third_fcc, 2*widths->max_fccfield_width/3);
1065 /* figure out which order they're in and reset widths */
1066 for(i = 0; i < NFIELDS; i++){
1067 pab->disp_form[i].width = 0;
1068 switch(pab->disp_form[i].type){
1069 case Nickname:
1070 nick = i;
1071 break;
1073 case Fullname:
1074 full = i;
1075 break;
1077 case Addr:
1078 addr = i;
1079 break;
1081 default:
1082 break;
1086 /* Compute default format */
1087 if(pab->disp_form[1].wtype == AllAuto){
1089 col1[0] = max_full;
1090 col2[0] = max_addr;
1091 col1[1] = third_full;
1092 col2[1] = third_addr;
1093 col1[2] = 3;
1094 col2[2] = 3;
1095 col1[3] = 2;
1096 col2[3] = 2;
1097 col1[4] = 1;
1098 col2[4] = 1;
1100 space_left = screen_width;
1102 if(pab->disp_form[0].type == Selected ||
1103 pab->disp_form[0].type == Checkbox){
1104 pab->disp_form[0].width = MIN(pab->disp_form[0].req_width,space_left);
1105 space_left = MAX(space_left-(pab->disp_form[0].width + SEP), 0);
1109 * All of the nickname field should be visible,
1110 * and make it at least 3.
1112 pab->disp_form[nick].width = MIN(MAX(max_nick, 3), space_left);
1115 * The SEP is for two blank columns between nickname and next field.
1116 * Those blank columns are taken automatically in paint_line().
1118 space_left -= (pab->disp_form[nick].width + SEP);
1120 if(space_left > 0){
1121 for(i = 0; i < 5; i++){
1122 /* try fitting most of each field in if possible */
1123 if(col1[i] + SEP + col2[i] <= space_left){
1124 int extra;
1126 extra = space_left - col1[i] - SEP - col2[i];
1128 * try to stabilize nickname column shifts
1129 * so that screen doesn't jump around when we make changes
1131 if(i == 0 && pab->disp_form[nick].width < 7 &&
1132 extra >= (7 - pab->disp_form[nick].width)){
1133 extra -= (7 - pab->disp_form[nick].width);
1134 space_left -= (7 - pab->disp_form[nick].width);
1135 pab->disp_form[nick].width = 7;
1138 pab->disp_form[addr].width = col2[i] + extra/2;
1139 pab->disp_form[full].width =
1140 space_left - SEP - pab->disp_form[addr].width;
1141 break;
1146 * None of them would fit. Toss addr field.
1148 if(i == 5){
1149 pab->disp_form[full].width = space_left;
1150 pab->disp_form[addr].width = 0;
1153 else{
1154 pab->disp_form[full].width = 0;
1155 pab->disp_form[addr].width = 0;
1158 dprint((10, "Using %s choice: %d %d %d", enth_string(i+1),
1159 pab->disp_form[nick].width, pab->disp_form[full].width,
1160 pab->disp_form[addr].width));
1162 else{ /* non-default case */
1163 int some_to_calculate = 0;
1164 int columns = 0;
1165 int used = 0;
1166 int avail_screen;
1167 int all_percents = 1;
1168 int pc_tot;
1171 * First count how many fields there are.
1172 * Fill in all the Fixed's while we're at it.
1174 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1175 if(pab->disp_form[i].wtype == Fixed){
1176 pab->disp_form[i].width = pab->disp_form[i].req_width;
1177 all_percents = 0;
1179 else if(pab->disp_form[i].wtype == WeCalculate){
1180 pab->disp_form[i].width = pab->disp_form[i].req_width; /* for now */
1181 some_to_calculate++;
1182 all_percents = 0;
1185 if(pab->disp_form[i].wtype != Special){
1186 used += pab->disp_form[i].width;
1187 columns++;
1191 used += ((columns-1) * SEP);
1192 avail_screen = screen_width - used;
1195 * Now that we know how much space we've got, we can
1196 * calculate the Percent columns.
1198 if(avail_screen > 0){
1199 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1200 if(pab->disp_form[i].wtype == Percent){
1201 /* The 2, 200, and +100 are because we're rounding */
1202 pab->disp_form[i].width =
1203 ((2*pab->disp_form[i].req_width*avail_screen)+100) / 200;
1204 used += pab->disp_form[i].width;
1209 space_left = screen_width - used;
1211 if(space_left < 0){
1213 * If they're all percentages, and the percentages add up to 100,
1214 * then we should fix the rounding problem.
1216 pc_tot = 0;
1217 if(all_percents){
1218 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++)
1219 if(pab->disp_form[i].wtype == Percent)
1220 pc_tot += pab->disp_form[i].req_width;
1223 /* fix the rounding problem */
1224 if(all_percents && pc_tot <= 100){
1225 int col = columns;
1226 int this_col = 0;
1227 int fix = used - screen_width;
1229 while(fix--){
1230 if(col < 0)
1231 col = columns;
1233 /* find col'th column */
1234 for(i=0, this_col=0; i < NFIELDS; i++){
1235 if(pab->disp_form[i].wtype == Percent){
1236 if(col == ++this_col)
1237 break;
1241 pab->disp_form[i].width--;
1242 col--;
1246 * Assume they meant to have them add up to over 100%, so we
1247 * just truncate the right hand edge.
1249 else{
1250 int this_fix, space_over;
1252 /* have to reduce space_over down to zero. */
1253 space_over = used - screen_width;
1254 for(i=NFIELDS-1; i >= 0 && space_over > 0; i--){
1255 if(pab->disp_form[i].type != Notused){
1256 this_fix = MIN(pab->disp_form[i].width, space_over);
1257 pab->disp_form[i].width -= this_fix;
1258 space_over -= this_fix;
1263 else if(space_left > 0){
1264 if(some_to_calculate){
1265 /* make nickname big enough to show all nicknames */
1266 if(nick >= 0 && pab->disp_form[nick].wtype == WeCalculate){
1267 --some_to_calculate;
1268 if(pab->disp_form[nick].width != max_nick){
1269 int this_fix;
1271 this_fix = MIN(max_nick-pab->disp_form[nick].width, space_left);
1272 pab->disp_form[nick].width += this_fix;
1273 space_left -= this_fix;
1277 if(!some_to_calculate && space_left > 0)
1278 goto none_to_calculate;
1280 if(space_left > 0){
1281 int weight = 0;
1282 int used_wt = 0;
1284 /* add up total weight */
1285 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1286 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1287 switch(pab->disp_form[i].type){
1288 case Fullname:
1289 weight += MAX(third_full, pab->disp_form[i].width);
1290 used_wt += pab->disp_form[i].width;
1291 break;
1293 case Addr:
1294 weight += MAX(third_addr, pab->disp_form[i].width);
1295 used_wt += pab->disp_form[i].width;
1296 break;
1298 case Filecopy:
1299 weight += MAX(third_fcc, pab->disp_form[i].width);
1300 used_wt += pab->disp_form[i].width;
1301 break;
1303 default:
1304 break;
1309 if(weight > 0){
1310 int this_fix;
1312 if(weight - used_wt <= space_left){
1313 for(i = 0;
1314 i < NFIELDS && pab->disp_form[i].type != Notused;
1315 i++){
1316 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1317 switch(pab->disp_form[i].type){
1318 case Fullname:
1319 this_fix = third_full - pab->disp_form[i].width;
1320 space_left -= this_fix;
1321 pab->disp_form[i].width += this_fix;
1322 break;
1324 case Addr:
1325 this_fix = third_addr - pab->disp_form[i].width;
1326 space_left -= this_fix;
1327 pab->disp_form[i].width += this_fix;
1328 break;
1330 case Filecopy:
1331 this_fix = third_fcc - pab->disp_form[i].width;
1332 space_left -= this_fix;
1333 pab->disp_form[i].width += this_fix;
1334 break;
1336 default:
1337 break;
1342 /* if still space left and a comment field, all to comment */
1343 if(space_left){
1344 for(i = 0;
1345 i < NFIELDS && pab->disp_form[i].type != Notused;
1346 i++){
1347 if(pab->disp_form[i].type == Comment &&
1348 pab->disp_form[i].wtype == WeCalculate){
1349 pab->disp_form[i].width += space_left;
1350 space_left = 0;
1355 else{ /* not enough space, dole out weighted pieces */
1356 int was_sl = space_left;
1358 for(i = 0;
1359 i < NFIELDS && pab->disp_form[i].type != Notused;
1360 i++){
1361 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1362 switch(pab->disp_form[i].type){
1363 case Fullname:
1364 /* round down */
1365 this_fix = (third_full * was_sl)/weight;
1366 space_left -= this_fix;
1367 pab->disp_form[i].width += this_fix;
1368 break;
1370 case Addr:
1371 this_fix = (third_addr * was_sl)/weight;
1372 space_left -= this_fix;
1373 pab->disp_form[i].width += this_fix;
1374 break;
1376 case Filecopy:
1377 this_fix = (third_fcc * was_sl)/weight;
1378 space_left -= this_fix;
1379 pab->disp_form[i].width += this_fix;
1380 break;
1382 default:
1383 break;
1390 /* give out rest */
1391 while(space_left > 0){
1392 for(i=NFIELDS-1; i >= 0 && space_left > 0; i--){
1393 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1394 pab->disp_form[i].width++;
1395 space_left--;
1401 else{
1403 * If they're all percentages, and the percentages add up to 100,
1404 * then we just have to fix a rounding problem. Otherwise, we'll
1405 * assume the user meant to have them add up to less than 100.
1407 none_to_calculate:
1408 pc_tot = 0;
1409 if(all_percents){
1410 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++)
1411 if(pab->disp_form[i].wtype == Percent)
1412 pc_tot += pab->disp_form[i].req_width;
1415 if(all_percents && pc_tot >= 100){
1416 int col = columns;
1417 int this_col = 0;
1418 int fix = screen_width - used;
1420 while(fix--){
1421 if(col < 0)
1422 col = columns;
1424 /* find col'th column */
1425 for(i=0, this_col=0; i < NFIELDS; i++){
1426 if(pab->disp_form[i].wtype == Percent){
1427 if(col == ++this_col)
1428 break;
1432 pab->disp_form[i].width++;
1433 col--;
1436 /* else, user specified less than 100%, leave it */
1439 /* else space_left == zero, nothing to do */
1442 * Check for special case. If we find it, this is the case where
1443 * we want to display the list entry field even though there is no
1444 * address field displayed. All of the display width is probably
1445 * used up by now, so we just need to pick some arbitrary width
1446 * for these lines. Since these lines are separate from the other
1447 * lines we've been calculating, we don't have to worry about running
1448 * into them, except for list continuation lines which we're not
1449 * going to worry about.
1451 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1452 if(pab->disp_form[i].wtype == Special){
1453 pab->disp_form[i].width = MIN(utf8_width(CLICKHERE), screen_width);
1454 break;
1459 /* check for width changes */
1460 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1461 if(pab->disp_form[i].width != pab->disp_form[i].old_width){
1462 ret++; /* Tell the caller the screen changed */
1463 pab->disp_form[i].old_width = pab->disp_form[i].width;
1467 pab->nick_is_displayed = 0;
1468 pab->full_is_displayed = 0;
1469 pab->addr_is_displayed = 0;
1470 pab->fcc_is_displayed = 0;
1471 pab->comment_is_displayed = 0;
1472 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1473 if(pab->disp_form[i].width > 0){
1474 switch(pab->disp_form[i].type){
1475 case Nickname:
1476 pab->nick_is_displayed++;
1477 break;
1478 case Fullname:
1479 pab->full_is_displayed++;
1480 break;
1481 case Addr:
1482 pab->addr_is_displayed++;
1483 break;
1484 case Filecopy:
1485 pab->fcc_is_displayed++;
1486 break;
1487 case Comment:
1488 pab->comment_is_displayed++;
1489 break;
1490 default:
1491 break;
1497 if(ret)
1498 dprint((9, " some widths changed\n"));
1500 return(ret);
1504 void
1505 redraw_addr_screen(void)
1507 dprint((7, "- redraw_addr_screen -\n"));
1509 ab_resize();
1510 if(as.l_p_page <= 0)
1511 return;
1513 (void)calculate_field_widths();
1514 display_book(0, as.cur_row, -1, 1, (Pos *)NULL);
1519 * Little front end for address book screen so it can be called out
1520 * of the main command loop in alpine.c
1522 void
1523 addr_book_screen(struct pine *pine_state)
1525 dprint((3, "\n\n --- ADDR_BOOK_SCREEN ---\n\n"));
1527 mailcap_free(); /* free resources we won't be using for a while */
1529 if(setjmp(addrbook_changed_unexpectedly)){
1530 /* TRANSLATORS: a warning message telling the user that the address book
1531 is being reset (re-sychronized, restarted) */
1532 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1533 dprint((1, "RESETTING address book... addr_book_screen!\n"));
1534 addrbook_reset();
1537 ab_nesting_level = 1; /* come here only from main menu */
1539 /* TRANSLATORS: a screen title for address book screen. Almost all of
1540 the translatable strings which are all upper case are screen titles. */
1541 (void)addr_book(AddrBookScreen, _("ADDRESS BOOK"), NULL);
1542 end_adrbks();
1544 pine_state->prev_screen = addr_book_screen;
1548 void
1549 addr_book_config(struct pine *pine_state, int edit_exceptions)
1551 if(edit_exceptions){
1552 q_status_message(SM_ORDER, 3, 7,
1553 _("Exception Setup not implemented for address books"));
1554 return;
1557 dprint((3, "\n\n --- ADDR_BOOK_CONFIG ---\n\n"));
1559 mailcap_free(); /* free resources we won't be using for a while */
1561 if(setjmp(addrbook_changed_unexpectedly)){
1562 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1563 dprint((1, "RESETTING address book... addr_book_config!\n"));
1564 addrbook_reset();
1567 ab_nesting_level = 1;
1569 /* TRANSLATORS: A screen title */
1570 (void)addr_book(AddrBookConfig, _("SETUP ADDRESS BOOKS"), NULL);
1571 end_adrbks();
1573 pine_state->prev_screen = addr_book_screen;
1578 * Return a single address
1580 * Returns: pointer to returned address, or NULL if nothing returned
1582 char *
1583 addr_book_oneaddr(void)
1585 char *p;
1586 jmp_buf save_jmp_buf;
1587 int *save_nesting_level;
1589 dprint((3, "--- addr_book_oneaddr ---\n"));
1591 save_nesting_level = cpyint(ab_nesting_level);
1592 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1593 if(setjmp(addrbook_changed_unexpectedly)){
1594 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1595 dprint((1, "RESETTING address book... addr_book_oneaddr!\n"));
1596 addrbook_reset();
1597 ab_nesting_level = *save_nesting_level;
1600 ab_nesting_level++;
1602 /* TRANSLATORS: a screen title */
1603 p = addr_book(SelectAddr, _("SELECT ADDRESS"), NULL);
1605 if(ab_nesting_level <= 1)
1606 end_adrbks();
1607 else
1608 ab_nesting_level--;
1610 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1611 if(save_nesting_level)
1612 fs_give((void **)&save_nesting_level);
1614 return(p);
1619 * Return a list of addresses without fullname
1621 * Returns: pointer to returned address, or NULL if nothing returned
1623 char *
1624 addr_book_multaddr_nf(void)
1626 char *p;
1627 jmp_buf save_jmp_buf;
1628 int *save_nesting_level;
1630 dprint((3, "--- addr_book_multaddr_nf ---\n"));
1632 save_nesting_level = cpyint(ab_nesting_level);
1633 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1634 if(setjmp(addrbook_changed_unexpectedly)){
1635 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1636 dprint((1, "RESETTING address book... addr_book_multaddr_nf!\n"));
1637 addrbook_reset();
1638 ab_nesting_level = *save_nesting_level;
1641 ab_nesting_level++;
1643 p = addr_book(SelectMultNoFull, _("SELECT ADDRESS"), NULL);
1645 if(ab_nesting_level <= 1)
1646 end_adrbks();
1647 else
1648 ab_nesting_level--;
1650 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1651 if(save_nesting_level)
1652 fs_give((void **)&save_nesting_level);
1654 return(p);
1659 * Return a single address without fullname phrase
1661 * Returns: pointer to returned address, or NULL if nothing returned
1663 char *
1664 addr_book_oneaddr_nf(void)
1666 char *p;
1667 jmp_buf save_jmp_buf;
1668 int *save_nesting_level;
1670 dprint((3, "--- addr_book_oneaddr_nf ---\n"));
1672 save_nesting_level = cpyint(ab_nesting_level);
1673 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1674 if(setjmp(addrbook_changed_unexpectedly)){
1675 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1676 dprint((1, "RESETTING address book... addr_book_oneaddr_nf!\n"));
1677 addrbook_reset();
1678 ab_nesting_level = *save_nesting_level;
1681 ab_nesting_level++;
1683 p = addr_book(SelectAddrNoFull, _("SELECT ADDRESS"), NULL);
1685 if(ab_nesting_level <= 1)
1686 end_adrbks();
1687 else
1688 ab_nesting_level--;
1690 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1691 if(save_nesting_level)
1692 fs_give((void **)&save_nesting_level);
1694 return(p);
1699 * Call address book from message composer
1701 * Args: error_mess -- pointer to return error messages in (unused here)
1703 * Returns: pointer to returned address, or NULL if nothing returned
1705 char *
1706 addr_book_compose(char **error)
1708 char *p;
1709 jmp_buf save_jmp_buf;
1710 int *save_nesting_level;
1712 dprint((3, "--- addr_book_compose ---\n"));
1714 save_nesting_level = cpyint(ab_nesting_level);
1715 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1716 if(setjmp(addrbook_changed_unexpectedly)){
1717 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1718 dprint((1, "RESETTING address book... addr_book_compose!\n"));
1719 addrbook_reset();
1720 ab_nesting_level = *save_nesting_level;
1723 ab_nesting_level++;
1725 /* TRANSLATORS: a screen title */
1726 p = addr_book(SelectNicksCom, _("COMPOSER: SELECT ADDRESS"), error);
1728 if(ab_nesting_level <= 1)
1729 end_adrbks();
1730 else
1731 ab_nesting_level--;
1733 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1734 if(save_nesting_level)
1735 fs_give((void **)&save_nesting_level);
1737 return(p);
1742 * Call address book from message composer for Lcc line
1744 * Args: error_mess -- pointer to return error messages in (unused here)
1746 * Returns: pointer to returned address, or NULL if nothing returned
1748 char *
1749 addr_book_compose_lcc(char **error)
1751 char *p;
1752 jmp_buf save_jmp_buf;
1753 int *save_nesting_level;
1755 dprint((3, "--- addr_book_compose_lcc ---\n"));
1757 save_nesting_level = cpyint(ab_nesting_level);
1758 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1759 if(setjmp(addrbook_changed_unexpectedly)){
1760 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1761 dprint((1, "RESETTING address book... addr_book_compose_lcc!\n"));
1762 addrbook_reset();
1763 ab_nesting_level = *save_nesting_level;
1766 ab_nesting_level++;
1769 * We used to use SelectAddrLccCom here but decided it wasn't necessary
1770 * to restrict the selection to a list.
1772 /* TRANSLATORS: a screen title, user is composing a message and should select
1773 a distribution list from a list of addresses in the address book. */
1774 p = addr_book(SelectNicksCom, _("COMPOSER: SELECT LIST"), error);
1776 if(ab_nesting_level <= 1)
1777 end_adrbks();
1778 else
1779 ab_nesting_level--;
1781 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1782 if(save_nesting_level)
1783 fs_give((void **)&save_nesting_level);
1785 return(p);
1790 * Call address book from message composer for Lcc line
1792 * Args: error_mess -- pointer to return error messages in (unused here)
1794 * Returns: pointer to returned address, or NULL if nothing returned
1796 char *
1797 addr_book_change_list(char **error)
1799 char *p;
1800 jmp_buf save_jmp_buf;
1801 int *save_nesting_level;
1803 dprint((3, "--- addr_book_change_list ---\n"));
1805 save_nesting_level = cpyint(ab_nesting_level);
1806 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1807 if(setjmp(addrbook_changed_unexpectedly)){
1808 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1809 dprint((1, "RESETTING address book... addr_book_change_list!\n"));
1810 addrbook_reset();
1811 ab_nesting_level = *save_nesting_level;
1814 ab_nesting_level++;
1816 /* TRANSLATORS: a screen title */
1817 p = addr_book(SelectNicksCom, _("ADDRESS BOOK (Update): SELECT ADDRESSES"),
1818 error);
1820 if(ab_nesting_level <= 1)
1821 end_adrbks();
1822 else
1823 ab_nesting_level--;
1825 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1826 if(save_nesting_level)
1827 fs_give((void **)&save_nesting_level);
1829 return(p);
1834 * Call address book from pine_simple_send
1836 * Returns: pointer to returned address, or NULL if nothing returned
1838 char *
1839 addr_book_bounce(void)
1841 char *p;
1842 jmp_buf save_jmp_buf;
1843 int *save_nesting_level;
1845 dprint((3, "- addr_book_bounce -\n"));
1847 save_nesting_level = cpyint(ab_nesting_level);
1848 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1849 if(setjmp(addrbook_changed_unexpectedly)){
1850 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1851 dprint((1, "RESETTING address book...addr_book_bounce!\n"));
1852 addrbook_reset();
1853 ab_nesting_level = *save_nesting_level;
1856 ab_nesting_level++;
1858 p = addr_book(SelectManyNicks, _("SELECT ADDRESSES"), NULL);
1860 if(ab_nesting_level <= 1)
1861 end_adrbks();
1862 else
1863 ab_nesting_level--;
1865 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1866 if(save_nesting_level)
1867 fs_give((void **)&save_nesting_level);
1869 return(p);
1874 * Call address book from take address screen
1876 * Returns: pointer to returned nickname, or NULL if nothing returned
1878 char *
1879 addr_book_takeaddr(void)
1881 char *p;
1882 jmp_buf save_jmp_buf;
1883 int *save_nesting_level;
1885 dprint((3, "- addr_book_takeaddr -\n"));
1887 save_nesting_level = cpyint(ab_nesting_level);
1888 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1889 if(setjmp(addrbook_changed_unexpectedly)){
1890 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1891 dprint((1, "RESETTING address book...addr_book_takeaddr!\n"));
1892 addrbook_reset();
1893 ab_nesting_level = *save_nesting_level;
1896 ab_nesting_level++;
1898 /* TRANSLATORS: a screen title */
1899 p = addr_book(SelectNickTake, _("TAKEADDR: SELECT NICKNAME"), NULL);
1901 if(ab_nesting_level <= 1)
1902 end_adrbks();
1903 else
1904 ab_nesting_level--;
1906 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1907 if(save_nesting_level)
1908 fs_give((void **)&save_nesting_level);
1910 return(p);
1915 * Call address book from editing screen for nickname field.
1917 * Returns: pointer to returned nickname, or NULL if nothing returned
1919 char *
1920 addr_book_nick_for_edit(char **error)
1922 char *p;
1923 jmp_buf save_jmp_buf;
1924 int *save_nesting_level;
1925 int save_n_serv;
1927 dprint((3, "- addr_book_nick_for_edit -\n"));
1929 save_n_serv = as.n_serv;
1931 save_nesting_level = cpyint(ab_nesting_level);
1932 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1933 if(setjmp(addrbook_changed_unexpectedly)){
1934 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1935 dprint((1, "RESETTING address book...addr_book_nick_for_edit!\n"));
1936 addrbook_reset();
1937 ab_nesting_level = *save_nesting_level;
1940 ab_nesting_level++;
1943 * This is kind of hoaky. We want to prevent the Directory Query
1944 * options from turning on when we're coming in looking for a nickname
1945 * and this seemed to be the easiest way to accomplish that.
1947 as.n_serv = 0;
1948 /* TRANSLATORS: a screen title, user selecting a nickname from the address book */
1949 p = addr_book(SelectNickCom, _("SELECT NICKNAME"), error);
1950 as.n_serv = save_n_serv;
1952 if(ab_nesting_level <= 1)
1953 end_adrbks();
1954 else
1955 ab_nesting_level--;
1957 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1958 if(save_nesting_level)
1959 fs_give((void **)&save_nesting_level);
1961 return(p);
1966 * Call address book for generic nickname select
1968 * Returns: pointer to returned nickname, or NULL if nothing returned
1970 char *
1971 addr_book_selnick(void)
1973 char *p;
1974 jmp_buf save_jmp_buf;
1975 int *save_nesting_level;
1977 dprint((3, "- addr_book_selnick -\n"));
1979 save_nesting_level = cpyint(ab_nesting_level);
1980 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1981 if(setjmp(addrbook_changed_unexpectedly)){
1982 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1983 dprint((1, "RESETTING address book...addr_book_selnick!\n"));
1984 addrbook_reset();
1985 ab_nesting_level = *save_nesting_level;
1988 ab_nesting_level++;
1990 p = addr_book(SelectNick, _("SELECT NICKNAME"), NULL);
1992 if(ab_nesting_level <= 1)
1993 end_adrbks();
1994 else
1995 ab_nesting_level--;
1997 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1998 if(save_nesting_level)
1999 fs_give((void **)&save_nesting_level);
2001 return(p);
2006 * Main address book screen
2008 * Control loop for address book. Commands are executed out of a big
2009 * switch and screen painting is done.
2010 * The style argument controls whether or not it is called to return an address
2011 * to the composer, a nickname to the TakeAddr screen, or just for address
2012 * book maintenance.
2014 * Args: style -- how we were called
2016 * Return: might return a string for the composer to edit, or a nickname, ...
2018 char *
2019 addr_book(AddrBookArg style, char *title, char **error_message)
2021 UCS c;
2022 int cmd, r, i,
2023 command_line,
2024 did_delete,
2025 quit, /* loop control */
2026 km_popped, /* menu is popped up in blank menu mode */
2027 current_changed_flag, /* only current row needs update */
2028 was_clickable, is_clickable,
2029 was_collapsible_listent, is_collapsible_listent,
2030 was_global_config, is_global_config,
2031 was_addkey, is_addkey,
2032 was_opened, is_opened,
2033 was_custom_title, is_custom_title,
2034 setall_changed,
2035 start_disp, /* Paint from this line down (0 is top) */
2036 rdonly, /* cur addrbook read only */
2037 empty, /* cur addrbook empty */
2038 are_selecting, /* called as ^T selector */
2039 #ifdef ENABLE_LDAP
2040 directory_ok, /* called from composer, not Lcc */
2041 #endif
2042 from_composer, /* from composer */
2043 listmode_ok, /* ok to do ListMode with this style */
2044 selecting_one_nick,
2045 selecting_mult_nicks,
2046 checkedn, /* how many are checked */
2047 def_key, /* default key */
2048 warped; /* we warped through hyperspace to a
2049 new location in the display list */
2050 long fl,
2051 new_top_ent, /* entry on top of screen after oper */
2052 new_line; /* new line number after operation */
2053 char *addr;
2054 char *utf8str;
2055 bitmap_t bitmap;
2056 struct key_menu *km;
2057 OtherMenu what;
2058 PerAddrBook *pab = NULL;
2059 AddrScrn_Disp *dl;
2060 struct pine *ps;
2061 Pos cursor_pos;
2063 dprint((2, "--- addr_book --- (%s)\n",
2064 style==AddrBookScreen ? "AddrBookScreen" :
2065 style==SelectAddrLccCom ? "SelectAddrLccCom" :
2066 style==SelectNicksCom ? "SelectNicksCom" :
2067 style==SelectNick ? "SelectNick" :
2068 style==SelectNickTake ? "SelectNickTake" :
2069 style==SelectNickCom ? "SelectNickCom" :
2070 style==AddrBookConfig ? "AddrBookConfig" :
2071 style==SelectManyNicks ? "SelectManyNicks" :
2072 style==SelectAddr ? "SelectAddr" :
2073 style==SelectAddrNoFull ? "SelectAddrNoFull" :
2074 style==SelectMultNoFull ? "SelectMultNoFull":
2075 "UnknownStyle"));
2077 km = &ab_keymenu;
2078 ps = ps_global;
2079 ps->next_screen = SCREEN_FUN_NULL;
2081 from_composer = (style == SelectAddrLccCom
2082 || style == SelectNicksCom
2083 || style == SelectNickCom);
2084 are_selecting = (style != AddrBookScreen
2085 && style != AddrBookConfig);
2086 selecting_one_nick = (style == SelectNick
2087 || style == SelectNickTake
2088 || style == SelectNickCom);
2089 selecting_mult_nicks = (style == SelectAddrLccCom
2090 || style == SelectNicksCom
2091 || style == SelectManyNicks);
2092 listmode_ok = (style == SelectAddrLccCom
2093 || style == SelectNicksCom
2094 || style == SelectManyNicks
2095 || style == SelectMultNoFull);
2096 as.config = (style == AddrBookConfig);
2097 #ifdef ENABLE_LDAP
2098 directory_ok = (style == SelectNicksCom
2099 || style == AddrBookScreen
2100 || style == SelectManyNicks);
2101 #endif
2103 /* Coming in from the composer, may need to reset the window */
2104 if(from_composer){
2105 fix_windsize(ps);
2106 init_sigwinch();
2107 mark_status_dirty();
2108 mark_titlebar_dirty();
2109 mark_keymenu_dirty();
2112 command_line = -FOOTER_ROWS(ps); /* third line from the bottom */
2113 what = FirstMenu;
2114 c = 'x'; /* For display_message the first time through */
2116 if(ps->remote_abook_validity > 0)
2117 (void)adrbk_check_and_fix_all(ab_nesting_level == 1, 0, 0);
2119 if(!init_addrbooks(HalfOpen, 1, !as.config, !are_selecting)){
2120 if(are_selecting){
2121 q_status_message(SM_ORDER | SM_DING, 0, 4,
2122 _("No Address Book Configured"));
2123 display_message(c);
2124 sleep(2);
2125 return NULL;
2127 else if(!as.config){
2128 ps->next_screen = main_menu_screen;
2129 q_status_message(SM_ORDER | SM_DING, 3, 4,
2130 _("No Address Book Configured, Use SETUP Addressbook screen"));
2131 ps->mangled_screen = 1;
2132 return NULL;
2135 else if(style == AddrBookScreen && as.n_addrbk == 1 && as.n_serv == 0){
2136 if(as.adrbks[0].access == ReadOnly)
2137 q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
2138 else if(as.adrbks[0].access == NoAccess)
2139 q_status_message(SM_ORDER, 0, 4,
2140 _("AddressBook not accessible, permission denied"));
2143 if(as.l_p_page < 1){
2144 q_status_message(SM_ORDER, 3, 3,
2145 _("Screen too small to use Address book"));
2146 return NULL;
2149 erase_checks();
2150 as.selections = 0;
2151 as.zoomed = 0;
2153 (void) calculate_field_widths();
2155 quit = 0;
2156 km_popped = 0;
2157 ps->mangled_screen = 1;
2158 current_changed_flag = 0;
2159 start_disp = 0;
2160 was_clickable = 0;
2161 is_clickable = 0;
2162 was_collapsible_listent = 0;
2163 is_collapsible_listent = 0;
2164 was_global_config = 0;
2165 is_global_config = 0;
2166 was_addkey = 0;
2167 is_addkey = 0;
2168 was_opened = 0;
2169 is_opened = 0;
2170 was_custom_title = 0;
2171 is_custom_title = 0;
2172 setall_changed = 0;
2173 checkedn = 0;
2176 while(!quit){
2177 ps->user_says_cancel = 0;
2178 if(km_popped){
2179 km_popped--;
2180 if(km_popped == 0){
2181 clearfooter(ps);
2183 * Have to repaint from earliest change down, including
2184 * at least the last two body lines.
2186 if(ps->mangled_body) /* it was already mangled */
2187 start_disp = MIN(start_disp, as.l_p_page -2);
2188 else if(current_changed_flag){
2189 ps->mangled_body = 1;
2190 start_disp = MIN(MIN(as.cur_row, as.l_p_page -2),
2191 as.old_cur_row);
2193 else{
2194 ps->mangled_body = 1;
2195 start_disp = as.l_p_page -2;
2200 ps->redrawer = redraw_addr_screen;
2202 if(new_mail(0, NM_TIMING(c), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
2203 ps->mangled_header = 1;
2205 if(streams_died())
2206 ps->mangled_header = 1;
2208 if(ps->mangled_screen){
2209 ps->mangled_header = 1;
2210 ps->mangled_body = 1;
2211 ps->mangled_footer = 1;
2212 start_disp = 0;
2213 current_changed_flag = 0;
2214 ps->mangled_screen = 0;
2217 if(ps->mangled_body){
2219 if(calculate_field_widths())
2220 start_disp = 0;
2222 display_book(start_disp,
2223 as.cur_row,
2224 as.old_cur_row,
2226 &cursor_pos);
2228 as.old_cur_row = as.cur_row;
2229 ps->mangled_body = 0;
2230 start_disp = 0;
2231 as.cur = cur_addr_book();
2232 pab = (as.n_addrbk &&
2233 as.cur >= 0 && as.cur < as.n_addrbk &&
2234 !entry_is_askserver(as.top_ent+as.cur_row))
2235 ? &as.adrbks[as.cur] : NULL;
2237 #ifdef _WINDOWS
2239 long i, last_sel;
2241 for(i = as.top_ent; dlist(i)->type != End; i++)
2242 next_selectable_line(i,&last_sel);
2243 as.last_ent = i;
2245 scroll_setrange(as.l_p_page, last_sel);
2247 #endif
2249 /* current entry has been changed */
2250 else if(current_changed_flag){
2251 int need_redraw;
2253 need_redraw = calculate_field_widths();
2255 /*---------- Update the current entry, (move or change) -------*/
2256 display_book(need_redraw ? 0 : as.cur_row,
2257 as.cur_row,
2258 as.old_cur_row,
2259 need_redraw,
2260 &cursor_pos);
2262 as.old_cur_row = as.cur_row;
2263 current_changed_flag = 0;
2264 as.cur = cur_addr_book();
2265 pab = (as.n_addrbk &&
2266 as.cur >= 0 && as.cur < as.n_addrbk &&
2267 !entry_is_askserver(as.top_ent+as.cur_row))
2268 ? &as.adrbks[as.cur] : NULL;
2271 is_custom_title = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) &&
2272 style == AddrBookScreen &&
2273 as.n_addrbk > 1 &&
2274 cur_is_open() &&
2275 pab->abnick &&
2276 pab->abnick[0]);
2277 if(( was_custom_title && !is_custom_title) ||
2278 (!was_custom_title && is_custom_title)){
2279 ps->mangled_header = 1;
2280 was_custom_title = is_custom_title;
2284 if(ps->mangled_header){
2285 char buf[80], *bp;
2287 if(style == AddrBookScreen){
2288 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
2289 if(as.n_addrbk > 1)
2290 snprintf(buf, sizeof(buf), _("ADDRESS BOOKS"));
2291 else
2292 snprintf(buf, sizeof(buf), _("ADDRESS BOOK"));
2294 else
2295 /* TRANSLATORS: a screen title, the %s arguments are for a custom part
2296 of the title. Move them as a group. There are two normal cases.
2297 Either ADDRESS BOOK LIST when a list of names of address books is
2298 displayed, or ADDRESS BOOK <name of address book> when one is open. */
2299 snprintf(buf, sizeof(buf), _("ADDRESS BOOK%s%s%s"),
2300 /* TRANSLATORS: This is the LIST that goes with title above */
2301 is_custom_title ? " <" : cur_is_open() ? "" : _(" LIST"),
2302 is_custom_title ? pab->abnick : "",
2303 is_custom_title ? ">" : "");
2305 buf[sizeof(buf)-1] = '\0';
2307 bp = buf;
2309 else
2310 bp = title;
2312 set_titlebar(bp, ps->mail_stream,
2313 ps->context_current, ps->cur_folder,
2314 ps->msgmap, 1,
2315 FolderName, 0, 0, NULL);
2316 ps->mangled_header = 0;
2319 dprint((9, "addr_book: top of loop, addrbk %d top_ent %ld cur_row %d\n", as.cur, as.top_ent, as.cur_row));
2322 * This is a check to catch when a move from one row to another
2323 * should cause the list of commands to change.
2325 is_clickable = entry_is_clickable(as.top_ent+as.cur_row);
2326 is_collapsible_listent = F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2327 entry_is_listent(as.top_ent+as.cur_row);
2328 is_global_config = as.config && pab && pab->type & GLOBAL;
2329 is_addkey = entry_is_addkey(as.top_ent+as.cur_row);
2330 is_opened = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) &&
2331 cur_is_open());
2332 if(( was_clickable && !is_clickable) ||
2333 (!was_clickable && is_clickable) ||
2334 ( was_collapsible_listent && !is_collapsible_listent) ||
2335 (!was_collapsible_listent && is_collapsible_listent) ||
2336 ( was_global_config && !is_global_config) ||
2337 (!was_global_config && is_global_config) ||
2338 ( was_addkey && !is_addkey) ||
2339 (!was_addkey && is_addkey) ||
2340 ( was_opened && !is_opened) ||
2341 (!was_opened && is_opened) ||
2342 ( setall_changed)){
2344 ps->mangled_footer = 1;
2345 was_clickable = is_clickable;
2346 was_collapsible_listent = is_collapsible_listent;
2347 was_global_config = is_global_config;
2348 was_addkey = is_addkey;
2349 was_opened = is_opened;
2350 setall_changed = 0;
2354 if(ps->mangled_footer){
2356 setbitmap(bitmap);
2357 menu_clear_binding(km, '>');
2358 menu_clear_binding(km, '.');
2359 menu_clear_binding(km, KEY_RIGHT);
2360 menu_clear_binding(km, ctrl('M'));
2361 menu_clear_binding(km, ctrl('J'));
2362 menu_clear_binding(km, KEY_LEFT);
2363 menu_clear_binding(km, '<');
2364 menu_clear_binding(km, ',');
2365 def_key = THREE_KEY; /* default default key */
2366 if(as.config){
2367 km->how_many = 1;
2369 clrbitn(OTHER_KEY, bitmap);
2370 /* TRANSLATORS: a command name for a particular key */
2371 menu_init_binding(km, 'E', MC_EXIT, "E", N_("Exit Setup"), TWO_KEY);
2372 KS_OSDATASET(&km->keys[TWO_KEY], KS_EXITMODE);
2375 * Don't show Delete or Shuffle command if there is nothing
2376 * to delete or shuffle.
2378 if(is_addkey){
2379 clrbitn(DELETE_KEY, bitmap);
2380 clrbitn(SENDTO_KEY, bitmap);
2381 clrbitn(THREE_KEY, bitmap);
2382 menu_init_binding(km, 'A', MC_ADDABOOK, "A",
2383 add_is_global(as.top_ent+as.cur_row)
2384 /* TRANSLATORS: Add Global Address book (one defined by someone else) */
2385 ? "[" N_("Add Glob Abook") "]"
2386 /* TRANSLATORS: Add Personal Address book */
2387 : "[" N_("Add Pers Abook") "]",
2388 ADD_KEY);
2389 def_key = ADD_KEY;
2391 else{
2392 /* TRANSLATORS: Delete Address book command */
2393 menu_init_binding(km, 'D', MC_DELABOOK, "D", N_("Del Abook"),
2394 DELETE_KEY);
2395 /* TRANSLATORS: Shuffle refers to shuffling the order of things,
2396 that is, changing the order */
2397 menu_init_binding(km, '$', MC_SHUFFLE, "$", N_("Shuffle"),
2398 SENDTO_KEY);
2399 /* TRANSLATORS: Change is a command meaning Change some item,
2400 or Edit some item to change it */
2401 menu_init_binding(km, 'C', MC_EDITABOOK, "C", "[" N_("Change") "]",
2402 THREE_KEY);
2403 menu_init_binding(km, 'A', MC_ADDABOOK, "A",
2404 add_is_global(as.top_ent+as.cur_row)
2405 ? N_("Add Glob Abook")
2406 : N_("Add Pers Abook"),
2407 ADD_KEY);
2410 else if(are_selecting){
2411 km->how_many = 1;
2414 * The OTHER_KEY is used as the Exit key in selection mode.
2415 * This is because the TWO_KEY is being used for < actions.
2417 /* TRANSLATORS: Command to Exit the Select screen. This would
2418 be a way to make no Selection and go back to where you
2419 came from. */
2420 menu_init_binding(km, 'E', MC_EXIT, "E", N_("ExitSelect"),
2421 OTHER_KEY);
2422 KS_OSDATASET(&km->keys[OTHER_KEY], KS_EXITMODE);
2425 * Use the TWO_KEY for the go back key.
2427 if(cur_is_open() && (as.n_addrbk > 1 || as.n_serv)){
2428 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2429 entry_is_listent(as.top_ent+as.cur_row))
2430 cmd = MC_UNEXPAND;
2431 else if(F_OFF(F_CMBND_ABOOK_DISP,ps_global))
2432 cmd = MC_POPUP;
2433 else
2434 cmd = MC_NONE;
2436 if(cmd == MC_NONE)
2437 clrbitn(TWO_KEY, bitmap);
2438 else{
2439 menu_init_binding(km, '<', cmd, "<",
2440 cmd == MC_POPUP ? N_("AddressBkList")
2441 /* TRANSLATORS: Unexpand is the opposite of Expand.
2442 We might expand an address book distribution
2443 list to see all the members of the list. */
2444 : N_("Unexpand"),
2445 TWO_KEY);
2446 menu_add_binding(km, ',', cmd);
2447 if(F_ON(F_ARROW_NAV,ps))
2448 menu_add_binding(km, KEY_LEFT, cmd);
2451 else if(as.checkboxes && (as.n_addrbk > 1 || as.n_serv)){
2452 if(checkedn){
2453 if(entry_is_clickable_title(as.top_ent+as.cur_row)){
2454 menu_init_binding(km, 'S', MC_CHOICE, "S",
2455 /* TRANSLATORS: Select something, choose something */
2456 N_("Select"), TWO_KEY);
2458 else{
2459 menu_init_binding(km, 'S', MC_CHOICE, "S",
2460 "[" N_("Select") "]", TWO_KEY);
2461 def_key = TWO_KEY;
2464 else
2465 menu_init_binding(km, 'S', MC_CHOICE, "S", N_("Select"),
2466 TWO_KEY);
2468 else
2469 clrbitn(TWO_KEY, bitmap);
2472 * The THREE_KEY is used as the select key in selection mode,
2473 * but it doesn't show up at the top-level. Instead, the
2474 * key becomes the ViewAbook key.
2476 if(entry_is_askserver(as.top_ent+as.cur_row) && !as.checkboxes){
2477 menu_init_binding(km, '>', MC_QUERY_SERV, ">", "[" N_("Search") "]",
2478 THREE_KEY);
2479 menu_add_binding(km, 's', MC_QUERY_SERV);
2480 menu_add_binding(km, '.', MC_QUERY_SERV);
2481 if(F_ON(F_ARROW_NAV,ps))
2482 menu_add_binding(km, KEY_RIGHT, MC_QUERY_SERV);
2484 else if(entry_is_clickable_title(as.top_ent+as.cur_row)){
2485 /* TRANSLATORS: View this address book */
2486 menu_init_binding(km, '>', MC_OPENABOOK, ">", "[" N_("ViewAbook") "]",
2487 THREE_KEY);
2488 menu_add_binding(km, 'v', MC_OPENABOOK);
2489 menu_add_binding(km, '.', MC_OPENABOOK);
2490 if(F_ON(F_ARROW_NAV,ps))
2491 menu_add_binding(km, KEY_RIGHT, MC_OPENABOOK);
2493 else if(cur_is_open()){
2494 menu_init_binding(km, 'S', MC_CHOICE, "S", "[" N_("Select") "]",
2495 THREE_KEY);
2497 else
2498 clrbitn(THREE_KEY, bitmap);
2500 KS_OSDATASET(&km->keys[THREE_KEY], KS_NONE);
2503 * The Expand command gets stuck out in right field.
2505 if(entry_is_clickable(as.top_ent+as.cur_row) &&
2506 !entry_is_clickable_title(as.top_ent+as.cur_row)){
2507 menu_init_binding(km, '>', MC_EXPAND, ">", N_("Expand"),
2508 SENDTO_KEY);
2509 menu_add_binding(km, '.', MC_EXPAND);
2510 if(F_ON(F_ARROW_NAV,ps))
2511 menu_add_binding(km, KEY_RIGHT, MC_EXPAND);
2513 else
2514 clrbitn(SENDTO_KEY, bitmap);
2516 if(cur_is_open() && as.checkboxes){
2517 /* TRANSLATORS: Set/Unset means that this particular command
2518 will toggle between setting something (turning it on) and
2519 unsetting it (turning it off). For example, it might be
2520 a program option that can be turned on or off or it might
2521 be a way to mark which addresses to send a message to. */
2522 menu_init_binding(km, 'X', MC_TOGGLE, "X", N_("Set/Unset"),
2523 DELETE_KEY);
2526 else if(cur_is_open() && listmode_ok){
2527 /* TRANSLATORS: List mode is a type of screen in pine that
2528 allows the user to select several of something. This is
2529 the name of the command to go into the List mode style
2530 of operating. */
2531 menu_init_binding(km, 'L', MC_LISTMODE, "L", N_("ListMode"),
2532 DELETE_KEY);
2534 else
2535 clrbitn(DELETE_KEY, bitmap);
2537 if(cur_is_open() && as.checkboxes){
2538 menu_init_binding(km, 'A', MC_SELALL, "A",
2539 /* TRANSLATORS: when selecting from a list of items
2540 the unsetall (unset all) means to start over
2541 with nothing selected.
2542 The set all command means select everything
2543 in the list. */
2544 checkedn ? N_("unsetAll") : N_("setAll"),
2545 ADD_KEY);
2547 else
2548 clrbitn(ADD_KEY, bitmap);
2550 KS_OSDATASET(&km->keys[DELETE_KEY], KS_NONE);
2552 else{
2554 * Reset first Other key. Selection screen may have
2555 * blasted it. Do this by hand because menu_init_binding
2556 * will remove the other two OTHER CMDS bindings.
2557 * Should figure out how to do this correctly with a
2558 * reasonable function call.
2560 km->keys[OTHER_KEY].name = "O";
2561 /* TRANSLATORS: This is the name of the command that will show
2562 which other commands are available. 12 commands are shown at
2563 the bottom of the screen, this command would show the next set
2564 of 12 */
2565 km->keys[OTHER_KEY].label = N_("OTHER CMDS");
2566 km->keys[OTHER_KEY].bind.cmd = MC_OTHER;
2567 km->keys[OTHER_KEY].bind.ch[0] = 'O';
2568 km->keys[OTHER_KEY].bind.nch = 1;
2569 KS_OSDATASET(&km->keys[OTHER_KEY], KS_NONE);
2571 km->how_many = 2;
2573 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
2574 if(F_ON(F_ENABLE_AGG_OPS, ps))
2575 km->how_many = 3;
2578 * The TWO_KEY is used as the go back key in
2579 * non-selection mode.
2581 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2582 entry_is_listent(as.top_ent+as.cur_row)){
2583 cmd = MC_UNEXPAND;
2584 menu_init_binding(km, '<', cmd, "<", N_("Unexpand"),
2585 TWO_KEY);
2586 KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
2588 else{
2589 cmd = MC_MAIN;
2590 menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
2591 TWO_KEY);
2592 KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
2595 if(cur_is_open()){
2597 * Add or delete entries from this address book.
2599 /* TRANSLATORS: Add a new entry (this is a command) */
2600 menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"),
2601 ADD_KEY);
2602 menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"),
2603 DELETE_KEY);
2604 /* TRANSLATORS: Compose a message to be sent to the current address
2605 book entry */
2606 menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"),
2607 SENDTO_KEY);
2608 KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER);
2609 menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"),
2610 RCOMPOSE_KEY);
2612 else{
2613 clrbitn(ADD_KEY, bitmap);
2614 clrbitn(DELETE_KEY, bitmap);
2615 clrbitn(SENDTO_KEY, bitmap);
2616 clrbitn(RCOMPOSE_KEY, bitmap);
2617 clrbitn(SAVE_KEY, bitmap);
2618 clrbitn(TAKE_KEY, bitmap);
2619 clrbitn(FORW_KEY, bitmap);
2622 clrbitn(SECONDARY_MAIN_KEY, bitmap);
2624 else if(cur_is_open()){
2625 if(F_ON(F_ENABLE_AGG_OPS, ps))
2626 km->how_many = 3;
2629 * The TWO_KEY is used as the go back key in
2630 * non-selection mode.
2632 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2633 entry_is_listent(as.top_ent+as.cur_row)){
2634 cmd = MC_UNEXPAND;
2635 menu_init_binding(km, '<', cmd, "<", N_("Unexpand"),
2636 TWO_KEY);
2637 KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
2639 else{
2640 if(as.n_addrbk > 1 || as.n_serv){
2641 cmd = MC_POPUP;
2642 menu_init_binding(km, '<', cmd, "<",
2643 N_("AddressBkList"), TWO_KEY);
2644 KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
2646 else{
2647 cmd = MC_MAIN;
2648 menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
2649 TWO_KEY);
2650 KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
2654 if(pab->access != NoAccess){
2656 * Add or delete entries from this address book.
2658 menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"),
2659 ADD_KEY);
2660 menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"),
2661 DELETE_KEY);
2663 else{
2664 clrbitn(ADD_KEY, bitmap);
2665 clrbitn(DELETE_KEY, bitmap);
2668 /* Find someplace to put Main Menu command */
2669 if(cmd == MC_POPUP){
2670 menu_init_binding(km, 'M', MC_MAIN, "M", N_("Main Menu"),
2671 SECONDARY_MAIN_KEY);
2672 KS_OSDATASET(&km->keys[SECONDARY_MAIN_KEY],KS_MAINMENU);
2674 else
2675 clrbitn(SECONDARY_MAIN_KEY, bitmap);
2677 menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"),
2678 SENDTO_KEY);
2679 KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER);
2680 menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"),
2681 RCOMPOSE_KEY);
2683 else{
2685 * The TWO_KEY is used as the go back key in
2686 * non-selection mode.
2688 cmd = MC_MAIN;
2689 menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
2690 TWO_KEY);
2691 KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
2693 clrbitn(SENDTO_KEY, bitmap);
2694 clrbitn(RCOMPOSE_KEY, bitmap);
2695 clrbitn(ADD_KEY, bitmap);
2696 clrbitn(DELETE_KEY, bitmap);
2697 clrbitn(SECONDARY_MAIN_KEY, bitmap);
2698 clrbitn(SAVE_KEY, bitmap);
2699 clrbitn(TAKE_KEY, bitmap);
2700 clrbitn(FORW_KEY, bitmap);
2703 /* can't be on third menu if we just reduced to 2 */
2704 if(km->how_many == 2 && km->which == 2)
2705 km->which = 0;
2707 menu_add_binding(km, '<', cmd);
2708 menu_add_binding(km, ',', cmd);
2709 if(F_ON(F_ARROW_NAV,ps))
2710 menu_add_binding(km, KEY_LEFT, cmd);
2712 KS_OSDATASET(&km->keys[DELETE_KEY], KS_DELETE);
2715 * The THREE_KEY is the burrow into the hierarchy key.
2717 if(entry_is_askserver(as.top_ent+as.cur_row))
2718 cmd = MC_QUERY_SERV;
2719 else if(entry_is_clickable(as.top_ent+as.cur_row)){
2720 if(entry_is_clickable_title(as.top_ent+as.cur_row))
2721 cmd = MC_OPENABOOK;
2722 else
2723 cmd = MC_EXPAND;
2725 else
2726 cmd = MC_VIEW_ENTRY;
2728 menu_init_binding(km, '>', cmd, ">",
2729 cmd == MC_EXPAND ? "[" N_("Expand") "]" :
2730 cmd == MC_QUERY_SERV ? "[" N_("Search") "]" :
2731 /* TRANSLATORS: In the address book the user can
2732 view a particular address book entry. It is
2733 called View/Update because the way to update
2734 an entry is to first view it and then there
2735 will be an opportunity to update it from there. */
2736 cur_is_open() ? "[" N_("View/Update") "]"
2737 : "[" N_("ViewAbook") "]",
2738 THREE_KEY);
2740 if(cmd == MC_QUERY_SERV)
2741 menu_add_binding(km, 's', cmd);
2742 else if(cmd == MC_OPENABOOK || cmd == MC_VIEW_ENTRY)
2743 menu_add_binding(km, 'v', cmd);
2745 menu_add_binding(km, '.', cmd);
2746 if(F_ON(F_ARROW_NAV,ps))
2747 menu_add_binding(km, KEY_RIGHT, cmd);
2750 menu_add_binding(km, ctrl('M'), km->keys[def_key].bind.cmd);
2751 menu_add_binding(km, ctrl('J'), km->keys[def_key].bind.cmd);
2753 if(km_popped){
2754 FOOTER_ROWS(ps) = 3;
2755 clearfooter(ps);
2758 draw_keymenu(km, bitmap, ps->ttyo->screen_cols,
2759 1-FOOTER_ROWS(ps), 0, what);
2760 ps->mangled_footer = 0;
2761 what = SameMenu;
2762 if(km_popped){
2763 FOOTER_ROWS(ps) = 1;
2764 mark_keymenu_dirty();
2768 rdonly = (pab && pab->access == ReadOnly);
2769 empty = is_empty(as.cur_row+as.top_ent);
2771 /*------------ display any status messages ------------------*/
2772 if(km_popped){
2773 FOOTER_ROWS(ps) = 3;
2774 mark_status_unknown();
2777 display_message(c);
2778 if(km_popped){
2779 FOOTER_ROWS(ps) = 1;
2780 mark_status_unknown();
2783 if(F_OFF(F_SHOW_CURSOR, ps)){
2784 /* reset each time through to catch screen size changes */
2785 cursor_pos.row = ps->ttyo->screen_rows-FOOTER_ROWS(ps);
2786 cursor_pos.col = 0;
2789 MoveCursor(cursor_pos.row, cursor_pos.col);
2792 /*---------------- Get command and validate -------------------*/
2793 #ifdef MOUSE
2794 mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);
2795 register_mfunc(mouse_in_content, HEADER_ROWS(ps), 0,
2796 ps->ttyo->screen_rows-(FOOTER_ROWS(ps)+1),
2797 ps->ttyo->screen_cols);
2798 #endif
2800 /* sort out what help needs to be displayed if asked for */
2801 if(as.config)
2802 gAbookHelp = h_abook_config;
2803 else if(are_selecting){
2804 if(cur_is_open()){
2805 if(style == SelectNickTake)
2806 gAbookHelp = h_abook_select_nicks_take;
2807 else if(selecting_one_nick)
2808 gAbookHelp = h_abook_select_nick;
2809 else if(as.checkboxes)
2810 gAbookHelp = h_abook_select_checks;
2811 else if(listmode_ok)
2812 gAbookHelp = h_abook_select_listmode;
2813 else
2814 gAbookHelp = h_abook_select_addr;
2816 else
2817 gAbookHelp = h_abook_select_top;
2819 else
2820 gAbookHelp = cur_is_open() ? h_abook_opened : h_abook_top;
2822 #ifdef _WINDOWS
2823 mswin_setscrollcallback(addr_scroll_callback);
2824 mswin_sethelptextcallback(pcpine_help_addrbook);
2825 #endif
2826 c = READ_COMMAND(&utf8str);
2827 #ifdef MOUSE
2828 clear_mfunc(mouse_in_content);
2829 #endif
2830 #ifdef _WINDOWS
2831 mswin_setscrollcallback(NULL);
2832 mswin_sethelptextcallback(NULL);
2833 #endif
2834 cmd = menu_command(c, km);
2836 dprint((2, "Addrbook command: %d (0x%x %s)\n", cmd,
2837 c, pretty_command(c)));
2839 /* this may be a safe place to update addrbooks */
2840 if(ps->remote_abook_validity > 0 &&
2841 adrbk_check_and_fix_all(ab_nesting_level < 2 &&
2842 !any_ab_open() && checkedn == 0, 0, 0))
2843 ps->mangled_footer = 1;
2845 if(km_popped)
2846 switch(cmd){
2847 case MC_NONE:
2848 case MC_OTHER:
2849 case MC_RESIZE:
2850 case MC_REPAINT:
2851 km_popped++;
2852 break;
2854 default:
2855 clearfooter(ps);
2856 break;
2859 /*------------- execute command ----------------*/
2860 switch(cmd){
2862 /*------------ Noop (new mail check) --------------*/
2863 case MC_NONE:
2864 break;
2867 /*----------- Help -------------------*/
2868 case MC_HELP:
2869 if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
2870 km_popped = 2;
2871 ps->mangled_footer = 1;
2872 break;
2875 if(as.config)
2876 helper(gAbookHelp, _("HELP ON CONFIGURING ADDRESS BOOKS"),
2877 HLPD_NONE);
2878 else if(are_selecting)
2879 helper(gAbookHelp, _("HELP ON ADDRESS BOOK"),
2880 HLPD_SIMPLE | HLPD_NEWWIN);
2881 else /* general maintenance screen */
2882 helper(gAbookHelp, _("HELP ON ADDRESS BOOK"), HLPD_NONE);
2885 * Helper() may have a Main Menu key. If user types that
2886 * they'll set next_screen. We don't have to do anything
2887 * special but we want to make sure that that doesn't happen
2888 * when we're selecting, even though it shouldn't be
2889 * possible because HLPD_SIMPLE is set.
2891 if(are_selecting)
2892 ps->next_screen = SCREEN_FUN_NULL; /* probably not needed */
2894 ps->mangled_screen = 1;
2895 break;
2898 /*---------- display other key bindings ------*/
2899 case MC_OTHER:
2900 warn_other_cmds();
2901 what = NextMenu;
2902 ps->mangled_footer = 1;
2903 break;
2906 /*------------ Unexpand list -----------------*/
2907 case MC_UNEXPAND:
2908 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2909 entry_is_listent(as.top_ent+as.cur_row)){
2910 DL_CACHE_S *dlc_to_flush;
2911 long global_row_num;
2914 * unexpand list
2918 * redraw screen starting with first ListEnt
2920 dl = dlist(as.top_ent+as.cur_row);
2921 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
2924 * New cur_row should be the line after the ListHead line
2925 * that takes the place of the first address in the list.
2927 as.cur_row = as.cur_row - dl->l_offset;
2930 * If the list header for the new row is off the screen,
2931 * adjust it.
2933 if(as.cur_row < 0){ /* center it */
2934 global_row_num = dlc_to_flush->global_row -
2935 dlc_to_flush->dlcoffset;
2936 as.top_ent = first_line(global_row_num - as.l_p_page/2L);
2937 as.cur_row = global_row_num - as.top_ent;
2938 start_disp = 0;
2940 else if(as.cur_row == 0){ /* just slide up in this case */
2941 as.top_ent -= 1;
2942 as.cur_row = 1;
2943 start_disp = 0;
2945 else
2946 start_disp = as.cur_row;
2948 exp_unset_expanded(pab->address_book->exp,
2949 (a_c_arg_t)dlc_to_flush->dlcelnum);
2950 flush_dlc_from_cache(dlc_to_flush);
2951 ps->mangled_body = 1;
2952 ps->mangled_footer = 1;
2954 else
2955 q_status_message(SM_ORDER | SM_DING, 3, 4,
2956 "Can't happen in MC_UNEXPAND");
2958 break;
2960 /*------ Popup to top level display ----------*/
2961 case MC_POPUP:
2962 if(F_ON(F_EXPANDED_DISTLISTS,ps) ||
2963 !entry_is_listent(as.top_ent+as.cur_row)){
2964 DL_CACHE_S dlc_restart;
2965 long new_row;
2967 (void)init_addrbooks((checkedn || as.selections)
2968 ? ThreeQuartOpen : HalfOpen,
2969 0, 0, !are_selecting);
2970 dlc_restart.adrbk_num = as.cur;
2971 dlc_restart.type = DlcTitle;
2972 warp_to_dlc(&dlc_restart, 0L);
2974 * Put the current entry in a nice spot on the screen.
2975 * Will everything above fit and still leave ours on screen?
2977 new_row = LINES_PER_ABOOK * as.cur +
2978 (((pab->type & GLOBAL) &&
2979 (as.how_many_personals > 0 || as.config))
2980 ? XTRA_LINES_BETWEEN : 0) +
2981 ((as.how_many_personals == 0 && as.config)
2982 ? LINES_PER_ADD_LINE : 0);
2983 new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
2985 as.cur_row = new_row;
2986 as.top_ent = 0L - as.cur_row;
2987 start_disp = 0;
2988 ps->mangled_screen = 1;
2990 else
2991 q_status_message(SM_ORDER | SM_DING, 3, 4,
2992 "Can't happen in MC_POPUP");
2994 break;
2997 /*------------- Back to main menu or exit to caller -------*/
2998 case MC_EXIT:
2999 case MC_MAIN:
3000 if(!are_selecting)
3001 ps->next_screen = main_menu_screen;
3003 if(!(are_selecting && as.checkboxes && checkedn > 0)
3004 /* TRANSLATORS: we are asking for confirmation about abandonding selections
3005 in the address book. */
3006 || want_to(_("Really abandon your selections "),
3007 'y', 'x', NO_HELP, WT_NORM) == 'y')
3008 quit++;
3009 else
3010 ps->next_screen = SCREEN_FUN_NULL;
3012 break;
3015 /*------- Open an address book ----------*/
3016 case MC_OPENABOOK:
3017 openabook:
3018 if(entry_is_clickable_title(as.top_ent+as.cur_row)){
3019 DL_CACHE_S *dlc_to_flush;
3021 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3022 if(dlc_to_flush->type == DlcTitle ||
3023 dlc_to_flush->type == DlcClickHereCmb){
3026 * open this addrbook and fill in display list
3029 init_abook(pab, Open);
3031 if(pab->access == ReadWrite || pab->access == ReadOnly){
3032 if(!are_selecting && pab->access == ReadOnly)
3033 q_status_message1(SM_ORDER, 0, 4, _("AddressBook %s is Read Only"), pab->abnick);
3036 * successful open, burrow into addrbook
3038 if(F_OFF(F_CMBND_ABOOK_DISP,ps_global)){
3039 warp_to_top_of_abook(as.cur);
3040 as.top_ent = 0L;
3041 as.cur_row = 0L;
3042 start_disp = 0;
3043 ps->mangled_screen = 1;
3045 else{
3046 flush_dlc_from_cache(dlc_to_flush);
3047 start_disp = as.cur_row;
3048 ps->mangled_footer = 1;
3049 ps->mangled_body = 1;
3052 else{ /* open failed */
3054 * Flush the title line so that it will change into
3055 * a permission denied line,
3057 flush_dlc_from_cache(dlc_to_flush);
3058 start_disp = as.cur_row;
3059 ps->mangled_footer = 1;
3060 ps->mangled_body = 1;
3063 else if(dlc_to_flush->type == DlcTitleNoPerm)
3064 q_status_message(SM_ORDER, 0, 4,
3065 _("Cannot access address book."));
3067 else
3068 q_status_message(SM_ORDER | SM_DING, 3, 4,
3069 "Can't happen in MC_OPENABOOK");
3071 break;
3074 /*------- Expand addresses in List ------*/
3075 case MC_EXPAND:
3076 expand:
3077 if(entry_is_clickable(as.top_ent+as.cur_row)){
3078 DL_CACHE_S *dlc_to_flush;
3080 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3081 if(dlc_to_flush->type == DlcListClickHere){
3082 start_disp = as.cur_row; /* will redraw from here down */
3085 * Mark this list expanded, then flush the
3086 * current line from dlc cache. When we get the
3087 * line again it will notice the expanded flag and change
3088 * the type to DlcListEnt (if any entries).
3091 if(F_OFF(F_EXPANDED_DISTLISTS,ps))
3092 exp_set_expanded(pab->address_book->exp,
3093 (a_c_arg_t)dlc_to_flush->dlcelnum);
3095 flush_dlc_from_cache(dlc_to_flush);
3096 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3098 * If list is empty, back cursor up one.
3100 if(dlc_to_flush->type == DlcListEmpty){
3101 as.cur_row--;
3102 start_disp--;
3103 if(as.cur_row < 0){
3104 as.top_ent--;
3105 as.cur_row = 0;
3106 start_disp = 0;
3110 ps->mangled_body = 1;
3113 else
3114 q_status_message(SM_ORDER | SM_DING, 3, 4,
3115 "Can't happen in MC_EXPAND");
3117 break;
3120 /*------- Select ---------------*/
3121 case MC_CHOICE:
3122 select:
3123 if(are_selecting){
3124 /* Select an entry to mail to or a nickname to add to */
3125 if(!any_addrs_avail(as.top_ent+as.cur_row)){
3126 q_status_message(SM_ORDER | SM_DING, 0, 4,
3127 _("No entries in address book. Use ExitSelect to leave address books"));
3128 break;
3131 if(as.checkboxes || is_addr(as.top_ent+as.cur_row)){
3132 BuildTo bldto;
3133 char *to = NULL;
3134 char *error = NULL;
3135 AdrBk_Entry *abe;
3137 dl = dlist(as.top_ent+as.cur_row);
3139 if(selecting_one_nick){
3140 char nickbuf[MAX_NICKNAME + 1];
3142 strncpy(nickbuf,
3143 ae(as.top_ent+as.cur_row)->nickname,
3144 sizeof(nickbuf)-1);
3145 nickbuf[sizeof(nickbuf)-1] = '\0';
3146 return(cpystr(nickbuf));
3148 else if(as.checkboxes && checkedn <= 0){
3149 q_status_message(SM_ORDER, 0, 1,
3150 _("Use \"X\" to mark addresses or lists"));
3151 break;
3153 else if(as.checkboxes){
3154 size_t incr = 100, avail, alloced;
3157 * Have to run through all of the checked entries
3158 * in all of the address books.
3159 * Put the nicknames together into one long
3160 * string with comma separators and let
3161 * our_build_address handle the parsing.
3163 to = (char *)fs_get(incr);
3164 *to = '\0';
3165 avail = incr;
3166 alloced = incr;
3168 for(i = 0; i < as.n_addrbk; i++){
3169 EXPANDED_S *next_one;
3170 adrbk_cntr_t num;
3171 AddrScrn_Disp fake_dl;
3172 char *a_string;
3174 pab = &as.adrbks[i];
3175 if(pab->address_book)
3176 next_one = pab->address_book->checks;
3177 else
3178 continue;
3180 while((num = entry_get_next(&next_one)) != NO_NEXT){
3181 abe = adrbk_get_ae(pab->address_book,
3182 (a_c_arg_t) num);
3184 * Since we're picking up address book entries
3185 * directly from the address books and have
3186 * no knowledge of the display lines they came
3187 * from, we don't know the dl's that go with
3188 * them. We need to pass a dl to abe_to_nick
3189 * but it really is only going to use the
3190 * type in this case.
3192 dl = &fake_dl;
3193 dl->type = (abe->tag == Single) ? Simple
3194 : ListHead;
3195 a_string = abe_to_nick_or_addr_string(abe, dl, i);
3197 while(abe && avail < (size_t)strlen(a_string)+1){
3198 alloced += incr;
3199 avail += incr;
3200 fs_resize((void **)&to, alloced);
3203 if(!*to){
3204 strncpy(to, a_string, alloced);
3205 to[alloced-1] = '\0';
3207 else{
3208 strncat(to, ",", alloced-strlen(to)-1);
3209 strncat(to, a_string, alloced-strlen(to)-1);
3212 avail -= (strlen(a_string) + 1);
3217 * Return the nickname list for lcc so that the
3218 * correct fullname can make it to the To line.
3219 * If we expand it ahead of time, the list name
3220 * and first user's fullname will get mushed together.
3221 * If an entry doesn't have a nickname then we're
3222 * out of luck as far as getting the right entry
3223 * in the To line goes.
3225 if(selecting_mult_nicks)
3226 return(to);
3228 bldto.type = Str;
3229 bldto.arg.str = to;
3231 else{
3232 /* Select an address, but not using checkboxes */
3233 if(selecting_mult_nicks){
3234 if(dl->type != ListHead && style == SelectAddrLccCom){
3235 q_status_message(SM_ORDER, 0, 4,
3236 _("You may only select lists for Lcc, use Bcc for other addresses"));
3237 break;
3239 else{
3241 * Even though we're supposedly selecting
3242 * nicknames, we have a special case here to
3243 * select a single member of a distribution
3244 * list. This happens with style SelectNicksCom
3245 * which is the regular ^T entry from the
3246 * composer, and it allows somebody to mail to
3247 * a single member of a distribution list.
3249 abe = ae(as.top_ent+as.cur_row);
3250 return(abe_to_nick_or_addr_string(abe, dl, as.cur));
3253 else{
3254 if(dl->type == ListEnt){
3255 bldto.type = Str;
3256 bldto.arg.str =
3257 listmem_from_dl(pab->address_book, dl);
3259 else{
3260 bldto.type = Abe;
3261 bldto.arg.abe = ae(as.top_ent+as.cur_row);
3266 (void)our_build_address(bldto, &addr, &error, NULL, save_and_restore);
3267 /* Have to rfc1522_decode the addr */
3268 if(addr){
3269 char *tmp_a_string, *p;
3270 ADDRESS *a = NULL;
3272 if(style == SelectAddrNoFull){
3273 tmp_a_string = cpystr(addr);
3274 rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain);
3275 fs_give((void **)&tmp_a_string);
3276 if(a){
3277 fs_give((void **)&addr);
3278 addr = cpystr(simple_addr_string(a, tmp_20k_buf,
3279 SIZEOF_20KBUF));
3280 mail_free_address(&a);
3283 else if(style == SelectMultNoFull){
3284 tmp_a_string = cpystr(addr);
3285 rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain);
3286 fs_give((void **)&tmp_a_string);
3287 if(a){
3288 fs_give((void **)&addr);
3289 addr = cpystr(simple_mult_addr_string(a,
3290 tmp_20k_buf,
3291 SIZEOF_20KBUF,
3292 ","));
3293 mail_free_address(&a);
3296 else{
3297 size_t len;
3298 len = 4*strlen(addr)+1;
3299 p = (char *)fs_get(len * sizeof(char));
3300 if(rfc1522_decode_to_utf8((unsigned char *)p, len, addr) == (unsigned char *)p){
3301 fs_give((void **)&addr);
3302 addr = p;
3304 else
3305 fs_give((void **)&p);
3309 if(to)
3310 fs_give((void **)&to);
3312 if(error){
3313 q_status_message1(SM_ORDER, 3, 4, "%s", error);
3314 fs_give((void **)&error);
3317 return(addr); /* Caller frees this */
3319 else{
3320 if(entry_is_clickable(as.top_ent+as.cur_row))
3321 clickable_warning(as.top_ent+as.cur_row);
3322 else if(entry_is_askserver(as.top_ent+as.cur_row))
3323 q_status_message(SM_ORDER, 3, 4, _("Use select to select an address or addresses from address books"));
3324 else
3325 q_status_message(SM_ORDER, 3, 4, _("No address selected"));
3327 break;
3330 else
3331 q_status_message(SM_ORDER | SM_DING, 3, 4,
3332 "Can't happen in MC_CHOICE");
3334 break;
3337 /*----- View an entry --------------------*/
3338 case MC_VIEW_ENTRY:
3339 view:
3340 if(empty){
3341 empty_warning(as.top_ent+as.cur_row);
3342 break;
3345 view_abook_entry(ps, as.top_ent+as.cur_row);
3346 break;
3349 /*----- Add new ---------*/
3350 case MC_ADD:
3351 {long old_l_p_p, old_top_ent, old_cur_row;
3353 if(adrbk_check_all_validity_now()){
3354 if(resync_screen(pab, style, checkedn)){
3355 q_status_message(SM_ORDER | SM_DING, 3, 4,
3356 _("Address book changed. AddNew cancelled. Try again."));
3357 ps->mangled_screen = 1;
3358 break;
3362 if(rdonly){
3363 q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
3364 break;
3367 warped = 0;
3368 dprint((9,
3369 "Calling edit_entry to add entry manually\n"));
3370 /* TRANSLATORS: add as in add a new entry to something */
3371 edit_entry(pab->address_book, (AdrBk_Entry *)NULL, NO_NEXT,
3372 NotSet, 0, &warped, _("add"));
3375 * Warped means we got plopped down somewhere in the display
3376 * list so that we don't know where we are relative to where
3377 * we were before we warped. The current line number will
3378 * be zero, since that is what the warp would have set.
3380 if(warped){
3381 as.top_ent = first_line(0L - as.l_p_page/2L);
3382 as.cur_row = 0L - as.top_ent;
3384 else{
3386 * If we didn't warp, that means we didn't change at all,
3387 * so keep old screen.
3389 old_l_p_p = as.l_p_page;
3390 old_top_ent = as.top_ent;
3391 old_cur_row = as.cur_row;
3394 /* Window size may have changed while in pico. */
3395 ab_resize();
3397 /* fix up what ab_resize messed up */
3398 if(!warped && old_l_p_p == as.l_p_page){
3399 as.top_ent = old_top_ent;
3400 as.cur_row = old_cur_row;
3401 as.old_cur_row = old_cur_row;
3405 ps->mangled_screen = 1;
3406 break;
3409 /*---------- Add a new address book -------------------*/
3410 case MC_ADDABOOK:
3411 {int old_abook_num, new_abook_num, new_row, global;
3412 int stay_put, old_cur_row;
3414 add_abook:
3415 stay_put = (dlist(as.top_ent+as.cur_row)->type == AddFirstPers ||
3416 dlist(as.top_ent+as.cur_row)->type == AddFirstGlob);
3417 old_cur_row = as.cur_row;
3418 old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
3419 global = (dlist(as.top_ent+as.cur_row)->type == AddFirstGlob ||
3420 (dlist(as.top_ent+as.cur_row)->type != AddFirstPers &&
3421 (old_abook_num >= as.how_many_personals) &&
3422 as.n_addrbk > 0));
3423 if((new_abook_num =
3424 ab_add_abook(global,
3425 stay_put ? -1
3426 : adrbk_num_from_lineno(as.top_ent+as.cur_row)))
3427 >= 0){
3428 DL_CACHE_S dlc_restart;
3430 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3432 erase_checks();
3433 erase_selections();
3435 dlc_restart.adrbk_num = new_abook_num;
3436 dlc_restart.type = DlcTitle;
3437 warp_to_dlc(&dlc_restart, 0L);
3440 * Put the current entry in a nice spot on the screen.
3442 new_row = old_cur_row +
3443 (stay_put
3445 : LINES_PER_ABOOK*(new_abook_num - old_abook_num));
3447 if(new_row >= 0 &&
3448 new_row <= (as.l_p_page-VIS_LINES_PER_ABOOK))/* ok, use it */
3449 as.cur_row = new_row;
3450 else{
3452 * Will everything above fit and still leave ours on screen?
3454 new_row = LINES_PER_ABOOK * new_abook_num +
3455 ((global &&
3456 (as.how_many_personals > 0 || as.config))
3457 ? XTRA_LINES_BETWEEN : 0) +
3458 ((as.how_many_personals == 0 && as.config)
3459 ? LINES_PER_ADD_LINE : 0);
3460 new_row = MAX(MIN(new_row,
3461 as.l_p_page - VIS_LINES_PER_ABOOK), 0);
3462 as.cur_row = new_row;
3465 as.top_ent = 0L - as.cur_row;
3466 dprint((5, "addrbook added: %s\n",
3467 as.adrbks[new_abook_num].filename
3468 ? as.adrbks[new_abook_num].filename : "?"));
3471 ps->mangled_screen = 1;
3475 break;
3478 /*---------- Change address book config -------------------*/
3479 case MC_EDITABOOK:
3480 change_abook:
3481 if(entry_is_clickable_title(as.top_ent+as.cur_row)){
3482 int abook_num, old_cur_row, global;
3483 long old_top_ent;
3484 DL_CACHE_S dlc_restart, *dlc;
3485 char *serv = NULL, *folder = NULL, *p, *q;
3486 char *nick = NULL, *file = NULL;
3488 abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
3489 global = (abook_num >= as.how_many_personals);
3490 old_cur_row = as.cur_row;
3491 old_top_ent = as.top_ent;
3492 dlc = get_dlc(as.top_ent+as.cur_row);
3493 dlc_restart = *dlc;
3495 if(global)
3496 q = ps_global->VAR_GLOB_ADDRBOOK[abook_num -
3497 as.how_many_personals];
3498 else
3499 q = ps_global->VAR_ADDRESSBOOK[abook_num];
3501 get_pair(q, &nick, &file, 0, 0);
3503 if(nick && !*nick)
3504 fs_give((void **)&nick);
3506 if(file && *file == '{'){
3507 q = file + 1;
3508 if((p = strindex(file, '}'))){
3509 *p = '\0';
3510 serv = q;
3511 folder = p+1;
3513 else{
3514 q_status_message1(SM_ORDER|SM_DING, 0, 4,
3515 _("Missing \"}\" in config: %s"), q);
3516 if(nick)
3517 fs_give((void **)&nick);
3518 if(file)
3519 fs_give((void **)&file);
3521 break;
3524 else
3525 folder = file;
3527 if(ab_edit_abook(global, abook_num, serv, folder, nick) >= 0){
3528 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3530 erase_checks();
3531 erase_selections();
3533 dlc_restart.type = DlcTitle;
3534 warp_to_dlc(&dlc_restart, old_top_ent+(long)old_cur_row);
3535 as.cur_row = old_cur_row;
3536 as.top_ent = old_top_ent;
3538 dprint((5, "addrbook config edited: %s\n",
3539 as.adrbks[dlc_restart.adrbk_num].filename
3540 ? as.adrbks[dlc_restart.adrbk_num].filename : "?"));
3543 if(nick)
3544 fs_give((void **)&nick);
3545 if(file)
3546 fs_give((void **)&file);
3548 ps->mangled_screen = 1;
3550 else
3551 /* TRANSLATORS: the user tried to change the current line of the address
3552 book but the line could not be changed */
3553 q_status_message(SM_ORDER, 0, 4, _("Not a changeable line"));
3555 break;
3558 /*---------- Delete an address book -------------------*/
3559 case MC_DELABOOK:
3560 if(as.n_addrbk == 0){
3561 q_status_message(SM_ORDER, 0, 4, _("Nothing to delete"));
3562 break;
3565 {char *err = NULL;
3567 if(ab_del_abook(as.top_ent+as.cur_row, command_line, &err) >= 0){
3568 DL_CACHE_S dlc_restart;
3569 int new_abook_num, old_abook_num, old_pers, old_glob, new_row;
3572 * Careful, these are only ok because ab_del_abook didn't
3573 * mess with the as globals, like as.how_many_personals.
3574 * The addrbook_reset does reset them, of course.
3576 old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
3577 old_pers = as.how_many_personals;
3578 old_glob = as.n_addrbk - as.how_many_personals;
3579 addrbook_reset();
3580 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3582 erase_checks();
3583 erase_selections();
3585 if(old_abook_num >= as.n_addrbk) /* we deleted last addrbook */
3586 new_abook_num = as.n_addrbk - 1;
3587 else
3588 new_abook_num = old_abook_num;
3591 * Pick a line to highlight and center
3593 if(as.how_many_personals == 0 && old_pers == 1)
3594 dlc_restart.type = DlcPersAdd;
3595 else if((as.n_addrbk - as.how_many_personals) == 0 &&
3596 old_glob == 1)
3597 dlc_restart.type = DlcGlobAdd;
3598 else if(as.n_addrbk == 0)
3599 dlc_restart.type = DlcPersAdd;
3600 else{
3601 dlc_restart.adrbk_num = new_abook_num;
3602 dlc_restart.type = DlcTitle;
3605 warp_to_dlc(&dlc_restart, 0L);
3608 * Will everything above fit and still leave ours on screen?
3610 if(dlc_restart.type == DlcTitle)
3611 new_row = LINES_PER_ABOOK * new_abook_num +
3612 (((as.adrbks[new_abook_num].type & GLOBAL) &&
3613 (as.how_many_personals > 0 || as.config))
3614 ? XTRA_LINES_BETWEEN : 0) +
3615 ((as.how_many_personals == 0 && as.config)
3616 ? LINES_PER_ADD_LINE : 0);
3617 else if(dlc_restart.type == DlcGlobAdd)
3618 new_row = LINES_PER_ABOOK * as.n_addrbk +
3619 ((as.how_many_personals > 0 || as.config)
3620 ? XTRA_LINES_BETWEEN : 0) +
3621 ((as.how_many_personals == 0 && as.config)
3622 ? LINES_PER_ADD_LINE : 0);
3623 else
3624 new_row = 0;
3626 new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
3627 as.cur_row = new_row;
3628 as.top_ent = 0L - as.cur_row;
3629 start_disp = 0;
3630 ps->mangled_body = 1;
3631 ps->mangled_footer = 1;
3632 /* TRANSLATORS: This is just comforting confirmation that the
3633 address book being deleted was successfully deleted */
3634 q_status_message(SM_ORDER, 0, 3, _("Address book deleted"));
3636 else{
3637 if(err){
3638 q_status_message(SM_ORDER, 0, 4, err);
3639 dprint((5, "addrbook delete failed: %s\n",
3640 err ? err : "?"));
3646 break;
3649 /*---- Reorder an addressbook list ---------*/
3650 case MC_SHUFFLE:
3651 if(entry_is_addkey(as.top_ent+as.cur_row)){
3652 q_status_message(SM_ORDER, 0, 4,
3653 _("Highlight entry you wish to shuffle"));
3654 break;
3657 {int slide;
3658 char *msg = NULL;
3659 int ret, new_anum;
3661 if((ret = ab_shuffle(pab, &slide, command_line, &msg)) > 0){
3662 DL_CACHE_S dlc_restart;
3663 int new_row;
3665 new_anum = ret - 1; /* see ab_shuffle return value */
3666 addrbook_reset();
3667 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3669 erase_checks();
3670 erase_selections();
3672 /* put cursor on new_anum */
3673 dlc_restart.adrbk_num = new_anum;
3674 dlc_restart.type = DlcTitle;
3675 warp_to_dlc(&dlc_restart, 0L);
3677 * Will everything above fit and still leave ours on screen?
3679 new_row = LINES_PER_ABOOK * new_anum +
3680 (((as.adrbks[new_anum].type & GLOBAL) &&
3681 (as.how_many_personals > 0 || as.config))
3682 ? XTRA_LINES_BETWEEN : 0) +
3683 ((as.how_many_personals == 0 && as.config)
3684 ? LINES_PER_ADD_LINE : 0);
3685 new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
3686 as.cur_row = new_row;
3687 as.top_ent = 0L - as.cur_row;
3688 start_disp = 0;
3689 ps->mangled_body = 1;
3691 else if(ret == 0){
3692 DL_CACHE_S *dlc_to_flush;
3694 if(slide < 0){ /* moved it up */
3695 as.cur_row += slide;
3696 start_disp = MAX(as.cur_row - 1, 0);
3697 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3699 * If we backed off the top of the screen, just
3700 * inch the display up so we can see it.
3702 if(as.cur_row < 0){
3703 as.top_ent += as.cur_row; /* cur_row is negative */
3704 as.cur_row = 0;
3705 start_disp = 0;
3708 else{ /* moved it down */
3709 start_disp = as.cur_row;
3710 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3711 as.cur_row += slide;
3712 if(as.cur_row > as.l_p_page - VIS_LINES_PER_ABOOK){
3713 as.top_ent += (as.cur_row -
3714 (as.l_p_page - VIS_LINES_PER_ABOOK));
3715 as.cur_row = MAX(as.l_p_page-VIS_LINES_PER_ABOOK, 0);
3716 start_disp = 0;
3720 flush_dlc_from_cache(dlc_to_flush);
3721 ps->mangled_body = 1;
3724 q_status_message(SM_ORDER, 0, 3,
3725 msg ? msg :
3726 (ret < 0) ? _("Shuffle failed") :
3727 _("Address books shuffled"));
3728 if(ret < 0)
3729 dprint((5, "addrbook shuffle failed: %s\n",
3730 msg ? msg : "?"));
3732 if(msg)
3733 fs_give((void **)&msg);
3737 break;
3740 /*----------------------- Move Up ---------------------*/
3741 case MC_CHARUP:
3742 case MC_PREVITEM:
3743 r = prev_selectable_line(as.cur_row+as.top_ent, &new_line);
3744 if(r == 0){
3745 /* find first line so everything is displayed */
3746 new_top_ent = as.cur_row+as.top_ent;
3747 for(dl=dlist(new_top_ent-1);
3748 dl->type != Beginning;
3749 dl = dlist((--new_top_ent) - 1))
3752 if(new_top_ent == as.top_ent ||
3753 (as.cur_row + (as.top_ent-new_top_ent) > as.l_p_page - 1)){
3754 q_status_message(SM_INFO, 0, 1, _("Already on first line."));
3756 else{
3757 as.cur_row += (as.top_ent - new_top_ent);
3758 as.top_ent = new_top_ent;
3759 start_disp = 0;
3760 ps->mangled_body = 1;
3763 break;
3766 i = ((cmd == MC_CHARUP)
3767 && dlist(as.top_ent - 1L)->type != Beginning)
3768 ? MIN(HS_MARGIN(ps), as.cur_row) : 0;
3769 as.cur_row = new_line - as.top_ent;
3770 if(as.cur_row - i < 0){
3771 if(cmd == MC_CHARUP){
3772 /*-- Past top of page --*/
3773 as.top_ent += (as.cur_row - i);
3774 as.cur_row = i;
3776 else{
3777 new_top_ent = first_line(as.top_ent - as.l_p_page);
3778 as.cur_row += (as.top_ent - new_top_ent);
3779 as.top_ent = new_top_ent;
3780 /* if it is still off screen */
3781 if(as.cur_row - i < 0){
3782 as.top_ent += (as.cur_row - i);
3783 as.cur_row = i;
3787 start_disp = 0;
3788 ps->mangled_body = 1;
3790 else
3791 current_changed_flag++;
3793 break;
3796 /*------------------- Move Down -------------------*/
3797 case MC_CHARDOWN:
3798 case MC_NEXTITEM:
3799 r = next_selectable_line(as.cur_row+as.top_ent, &new_line);
3800 if(r == 0){
3801 long new_end_line;
3803 /* find last line so everything is displayed */
3804 new_end_line = as.cur_row+as.top_ent;
3805 for(dl=dlist(new_end_line+1);
3806 dl->type != End;
3807 dl = dlist((++new_end_line)+1))
3810 if(new_end_line - as.top_ent <= as.l_p_page - 1 ||
3811 as.cur_row - (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){
3812 q_status_message(SM_INFO, 0, 1, _("Already on last line."));
3814 else{
3815 as.cur_row -= (new_end_line-as.top_ent-(as.l_p_page-1));
3816 as.top_ent += (new_end_line-as.top_ent-(as.l_p_page-1));
3817 start_disp = 0;
3818 ps->mangled_body = 1;
3821 break;
3824 /* adjust for scrolling margin */
3825 i = (cmd == MC_CHARDOWN) ? HS_MARGIN(ps) : 0;
3826 as.cur_row = new_line - as.top_ent;
3827 if(as.cur_row >= as.l_p_page - i){
3828 if(cmd == MC_CHARDOWN){
3829 /*-- Past bottom of page --*/
3830 as.top_ent += (as.cur_row - (as.l_p_page - i) + 1);
3831 as.cur_row = (as.l_p_page - i) - 1;
3833 else{
3834 /*-- Changed pages --*/
3835 as.top_ent += as.l_p_page;
3836 as.cur_row -= as.l_p_page;
3837 /* if it is still off screen */
3838 if(as.cur_row >= as.l_p_page - i){
3839 as.top_ent += (as.cur_row - (as.l_p_page - i) + 1);
3840 as.cur_row = (as.l_p_page - i) - 1;
3844 start_disp = 0;
3845 ps->mangled_body = 1;
3847 else
3848 current_changed_flag++;
3850 break;
3853 #ifdef MOUSE
3854 case MC_MOUSE:
3856 MOUSEPRESS mp;
3859 * Get the mouse down. Convert to content row number.
3860 * If the row is selectable, do the single or double click
3861 * operation.
3863 mouse_get_last(NULL, &mp);
3864 mp.row -= HEADER_ROWS(ps);
3865 if(line_is_selectable(as.top_ent + mp.row)){
3866 if(mp.button == M_BUTTON_LEFT){
3867 if(mp.doubleclick){
3869 * A mouse double click does the default action to
3870 * the selected line. Since we need to do a goto
3871 * to get there, we have this ugly bit of code.
3873 * On the first mouse click we go around the loop
3874 * once and set def_key appropriately for the
3875 * line as.top_ent+mp.row, which will then be
3876 * the same as as.top_ent+as.cur_row.
3878 switch(km->keys[def_key].bind.cmd){
3879 case MC_CHOICE:
3880 if (as.checkboxes) goto togglex;
3881 else goto select;
3882 case MC_OPENABOOK:
3883 goto openabook;
3884 case MC_EXPAND:
3885 goto expand;
3886 case MC_TOGGLE:
3887 goto togglex;
3888 case MC_SELALL:
3889 goto selall;
3890 case MC_VIEW_ENTRY:
3891 goto view;
3892 case MC_ADDABOOK:
3893 goto add_abook;
3894 case MC_EDITABOOK:
3895 goto change_abook;
3896 #ifdef ENABLE_LDAP
3897 case MC_QUERY_SERV:
3898 goto q_server;
3899 #endif
3900 default:
3901 q_status_message(SM_INFO, 0, 1,
3902 "Can't happen in MC_MOUSE");
3903 break;
3907 as.cur_row = mp.row;
3908 current_changed_flag++;
3910 else if(mp.button == M_BUTTON_RIGHT){
3911 #ifdef _WINDOWS
3912 int need_redraw;
3913 #endif
3914 as.cur_row = mp.row;
3915 current_changed_flag++;
3918 #ifdef _WINDOWS
3919 need_redraw = calculate_field_widths();
3921 /*---------- Update the current entry, (move or change) -------*/
3922 display_book(need_redraw ? 0 : as.cur_row,
3923 as.cur_row,
3924 as.old_cur_row,
3925 need_redraw,
3926 &cursor_pos);
3928 as.old_cur_row = as.cur_row;
3929 current_changed_flag = 0;
3930 as.cur = cur_addr_book();
3931 pab = (as.n_addrbk &&
3932 !entry_is_askserver(as.top_ent+as.cur_row))
3933 ? &as.adrbks[as.cur] : NULL;
3935 if(!mp.doubleclick){
3936 MPopup addr_pu[20];
3937 int n, i;
3939 /* Make what they clicked "current" */
3940 memset(addr_pu, 0, 20 * sizeof(MPopup));
3942 addr_pu[n = 0].type = tQueue;
3943 addr_pu[n].data.val = km->keys[def_key].bind.ch[0];
3944 addr_pu[n].label.style = lNormal;
3945 addr_pu[n++].label.string = km->keys[def_key].label;
3947 if((i = menu_clear_binding(km, '<')) != MC_UNKNOWN){
3948 menu_add_binding(km, '<', i);
3950 if((i = menu_binding_index(km, i)) >= 0){
3951 addr_pu[n++].type = tSeparator;
3953 addr_pu[n].type = tQueue;
3954 addr_pu[n].data.val = km->keys[i].bind.ch[0];
3955 addr_pu[n].label.style = lNormal;
3956 addr_pu[n++].label.string = km->keys[i].label;
3960 addr_pu[n].type = tTail;
3962 mswin_popup(addr_pu);
3964 #endif
3968 break;
3969 #endif
3972 /*------------- Page Up or Down --------*/
3973 case MC_PAGEUP:
3974 case MC_PAGEDN:
3975 if(cmd == MC_PAGEUP){
3976 /* find first line on prev page */
3977 new_top_ent = first_line(as.top_ent - as.l_p_page);
3978 if(new_top_ent == NO_LINE)
3979 break;
3981 /* find first selectable line */
3982 fl = first_selectable_line(new_top_ent);
3984 /* If we didn't move, we'd better move now */
3985 if(fl == as.top_ent+as.cur_row){
3986 if(!prev_selectable_line(as.cur_row+as.top_ent, &new_line)){
3987 long lineno;
3989 /* find first line so everything is displayed */
3990 lineno = as.cur_row+as.top_ent;
3991 for(dl=dlist(lineno);
3992 dl->type != Beginning;
3993 dl = dlist(--lineno))
3997 * If this new_top_ent is the same as the old_top_ent
3998 * we'll get the warning message.
4000 new_top_ent = first_line(lineno);
4001 if(fl - new_top_ent >= as.l_p_page)
4002 new_top_ent += (fl - new_top_ent - as.l_p_page + 1);
4004 else
4005 fl = new_line;
4008 if(fl == NO_LINE)
4009 break;
4011 if(as.top_ent == new_top_ent && as.cur_row == (fl-as.top_ent)){
4012 q_status_message(SM_INFO, 0, 1, _("Already on first page."));
4013 break;
4016 if(as.top_ent == new_top_ent)
4017 current_changed_flag++;
4018 else
4019 as.top_ent = new_top_ent;
4021 else{ /* Down */
4022 /* find first selectable line on next page */
4023 fl = first_selectable_line(as.top_ent + as.l_p_page);
4024 if(fl == NO_LINE)
4025 break;
4027 /* if there is another page, scroll */
4028 if(fl - as.top_ent >= as.l_p_page){
4029 new_top_ent = as.top_ent + as.l_p_page;
4031 /* on last page already */
4032 else{
4033 new_top_ent = as.top_ent;
4034 if(as.cur_row == (fl - as.top_ent)){ /* no change */
4035 long new_end_line;
4037 /* find last line so everything is displayed */
4038 new_end_line = as.cur_row+as.top_ent;
4039 for(dl=dlist(new_end_line+1);
4040 dl->type != End;
4041 dl = dlist((++new_end_line)+1))
4044 if(new_end_line - as.top_ent <= as.l_p_page - 1 ||
4045 as.cur_row -
4046 (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){
4047 q_status_message(SM_INFO, 0, 1,
4048 _("Already on last page."));
4050 else{
4051 as.cur_row -=
4052 (new_end_line-as.top_ent-(as.l_p_page-1));
4053 as.top_ent +=
4054 (new_end_line-as.top_ent-(as.l_p_page-1));
4055 start_disp = 0;
4056 ps->mangled_body = 1;
4059 break;
4063 if(as.top_ent == new_top_ent)
4064 current_changed_flag++;
4065 else
4066 as.top_ent = new_top_ent;
4070 * Stuff in common for up or down.
4072 as.cur_row = fl - as.top_ent;
4074 /* if it is still off screen */
4075 if(as.cur_row < 0){
4076 as.top_ent += as.cur_row;
4077 as.cur_row = 0;
4079 else if(as.cur_row >= as.l_p_page){
4080 as.top_ent += (as.cur_row - as.l_p_page + 1);
4081 as.cur_row = as.l_p_page - 1;
4084 if(!current_changed_flag){
4085 ps->mangled_body = 1;
4086 start_disp = 0;
4089 break;
4092 /*------------- Delete item from addrbook ---------*/
4093 case MC_DELETE:
4094 if(adrbk_check_all_validity_now()){
4095 if(resync_screen(pab, style, checkedn)){
4096 q_status_message(SM_ORDER | SM_DING, 3, 4,
4097 _("Address book changed. Delete cancelled. Try again."));
4098 ps->mangled_screen = 1;
4099 break;
4103 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4104 q_status_message(SM_ORDER, 0, 4, _("No entries to delete"));
4105 break;
4108 if(entry_is_clickable(as.top_ent+as.cur_row)){
4109 clickable_warning(as.top_ent+as.cur_row);
4110 break;
4113 if(rdonly){
4114 q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
4115 break;
4118 if(empty){
4119 empty_warning(as.top_ent+as.cur_row);
4120 break;
4123 warped = 0;
4124 did_delete = single_entry_delete(pab->address_book,
4125 as.cur_row+as.top_ent,
4126 &warped);
4127 ps->mangled_footer = 1;
4128 if(did_delete){
4129 if(warped){
4130 as.top_ent = first_line(0L - as.l_p_page/2L);
4131 as.cur_row = 0L - as.top_ent;
4132 start_disp = 0;
4134 else{
4136 * In case the line we're now at is not a selectable
4137 * field.
4139 new_line = first_selectable_line(as.cur_row+as.top_ent);
4140 if(new_line != NO_LINE
4141 && new_line != as.cur_row+as.top_ent){
4142 as.cur_row = new_line - as.top_ent;
4143 if(as.cur_row < 0){
4144 as.top_ent -= as.l_p_page;
4145 as.cur_row += as.l_p_page;
4147 else if(as.cur_row >= as.l_p_page){
4148 as.top_ent += as.l_p_page;
4149 as.cur_row -= as.l_p_page;
4153 start_disp = MIN(as.cur_row, as.old_cur_row);
4156 ps->mangled_body = 1;
4159 break;
4162 /*------------- Toggle checkbox ---------*/
4163 case MC_TOGGLE:
4164 togglex:
4165 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4166 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4167 break;
4170 if(entry_is_clickable(as.top_ent+as.cur_row)){
4171 clickable_warning(as.top_ent+as.cur_row);
4172 break;
4175 if(empty){
4176 empty_warning(as.top_ent+as.cur_row);
4177 break;
4180 if(is_addr(as.top_ent+as.cur_row)){
4181 dl = dlist(as.top_ent+as.cur_row);
4183 if(style == SelectAddrLccCom && dl->type == ListEnt)
4184 q_status_message(SM_ORDER, 0, 4,
4185 _("You may only select whole lists for Lcc"));
4186 else if(style == SelectAddrLccCom && dl->type != ListHead)
4187 q_status_message(SM_ORDER, 0, 4,
4188 _("You may only select lists for Lcc, use Bcc for personal entries"));
4189 else if(dl->type == ListHead || dl->type == Simple){
4190 current_changed_flag++;
4191 if(entry_is_checked(pab->address_book->checks,
4192 (a_c_arg_t)dl->elnum)){
4193 entry_unset_checked(pab->address_book->checks,
4194 (a_c_arg_t)dl->elnum);
4195 checkedn--;
4196 if(checkedn == 0)
4197 setall_changed++;
4199 else{
4200 entry_set_checked(pab->address_book->checks,
4201 (a_c_arg_t)dl->elnum);
4202 if(checkedn == 0)
4203 setall_changed++;
4205 checkedn++;
4208 else
4209 q_status_message(SM_ORDER, 0, 4,
4210 _("You may not select list members, only whole lists or personal entries"));
4212 else
4213 q_status_message(SM_ORDER, 0, 4,
4214 _("You may only select addresses or lists"));
4216 break;
4219 /*------ Turn all checkboxes on ---------*/
4220 case MC_SELALL:
4221 selall:
4222 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4223 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4224 break;
4227 if(entry_is_clickable(as.top_ent+as.cur_row)){
4228 clickable_warning(as.top_ent+as.cur_row);
4229 break;
4232 if(empty){
4233 empty_warning(as.top_ent+as.cur_row);
4234 break;
4238 adrbk_cntr_t num, ab_count;
4240 ab_count = adrbk_count(pab->address_book);
4241 setall_changed++;
4242 if(checkedn){ /* unset All */
4243 for(num = 0; num < ab_count; num++){
4244 if(entry_is_checked(pab->address_book->checks,
4245 (a_c_arg_t)num)){
4246 entry_unset_checked(pab->address_book->checks,
4247 (a_c_arg_t)num);
4248 checkedn--;
4252 else{ /* set All */
4253 for(num = 0; num < ab_count; num++){
4254 if(!entry_is_checked(pab->address_book->checks,
4255 (a_c_arg_t)num)){
4256 entry_set_checked(pab->address_book->checks,
4257 (a_c_arg_t)num);
4258 checkedn++;
4263 ps->mangled_body = 1;
4264 start_disp = 0;
4267 break;
4270 /*---------- Turn on ListMode -----------*/
4271 case MC_LISTMODE:
4272 as.checkboxes = 1;
4273 for(i = 0; i < as.n_addrbk; i++){
4274 pab = &as.adrbks[i];
4275 init_disp_form(pab, ps->VAR_ABOOK_FORMATS, i);
4278 (void)calculate_field_widths();
4279 ps->mangled_footer = 1;
4280 ps->mangled_body = 1;
4281 start_disp = 0;
4282 q_status_message(SM_ORDER, 0, 4,
4283 _("Use \"X\" to select addresses or lists"));
4284 break;
4287 /*--------- Compose -----------*/
4288 case MC_COMPOSE:
4289 (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0);
4290 break;
4293 /*--------- Alt Compose -----------*/
4294 case MC_ROLE:
4295 (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1);
4296 break;
4299 #ifdef ENABLE_LDAP
4300 /*------ Query Directory ------*/
4301 case MC_QUERY_SERV:
4302 q_server:
4303 {char *err_mess = NULL, **err_mess_p;
4304 int query_type;
4306 if(!directory_ok){
4307 q_status_message(SM_ORDER, 0, 4,
4308 (style == SelectAddrLccCom)
4309 ? "Can't search server for Lcc"
4310 : "Can't search server from here");
4311 break;
4313 else if(as.checkboxes){
4314 q_status_message(SM_ORDER, 0, 4,
4315 "Can't search server when using ListMode");
4316 break;
4319 if(error_message)
4320 err_mess_p = error_message;
4321 else
4322 err_mess_p = &err_mess;
4325 * The query lines are indexed by their adrbk_num. (Just using
4326 * that because it was handy and not used for addrbooks.)
4328 query_type = adrbk_num_from_lineno(as.top_ent+as.cur_row);
4330 if((addr = query_server(ps, are_selecting, &quit, query_type,
4331 err_mess_p)) != NULL){
4332 if(are_selecting)
4333 return(addr);
4335 fs_give((void **)&addr);
4338 if(err_mess_p && *err_mess_p && !error_message){
4339 q_status_message(SM_ORDER, 0, 4, *err_mess_p);
4340 fs_give((void **)err_mess_p);
4344 break;
4345 #endif /* ENABLE_LDAP */
4348 /*----------- Where is (search) ----------------*/
4349 case MC_WHEREIS:
4350 warped = 0;
4351 new_top_ent = ab_whereis(&warped, command_line);
4353 if(new_top_ent != NO_LINE){
4354 if(warped || new_top_ent != as.top_ent){
4355 as.top_ent = new_top_ent;
4356 start_disp = 0;
4357 ps->mangled_body = 1;
4359 else
4360 current_changed_flag++;
4363 ps->mangled_footer = 1;
4364 break;
4367 /*----- Select entries to work on --*/
4368 case MC_SELECT:
4369 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4370 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4371 break;
4374 if(!cur_is_open()){
4375 if(entry_is_askserver(as.top_ent+as.cur_row))
4376 q_status_message(SM_ORDER, 0, 4,
4377 _("Select is only available from within an expanded address book"));
4378 else
4379 clickable_warning(as.top_ent+as.cur_row);
4381 break;
4384 dl = dlist(as.top_ent+as.cur_row);
4385 if(dl->type == Empty){
4386 empty_warning(as.top_ent+as.cur_row);
4387 break;
4390 {int were_selections = as.selections;
4392 ab_select(ps, pab ? pab->address_book : NULL,
4393 as.top_ent+as.cur_row, command_line, &start_disp);
4395 if((!were_selections && as.selections)
4396 || (were_selections && !as.selections)){
4397 ps->mangled_footer = 1;
4398 for(i = 0; i < as.n_addrbk; i++)
4399 init_disp_form(&as.adrbks[i],
4400 ps->VAR_ABOOK_FORMATS, i);
4404 break;
4407 /*----------- Select current entry ----------*/
4408 case MC_SELCUR:
4409 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4410 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4411 break;
4414 if(entry_is_clickable(as.top_ent+as.cur_row)){
4415 clickable_warning(as.top_ent+as.cur_row);
4416 break;
4419 if(empty){
4420 empty_warning(as.top_ent+as.cur_row);
4421 break;
4424 if(is_addr(as.top_ent+as.cur_row)){
4425 dl = dlist(as.top_ent+as.cur_row);
4427 if(dl->type == ListHead || dl->type == Simple){
4428 int do_init_disp = 0;
4429 long ll;
4431 current_changed_flag++;
4433 if(entry_is_selected(pab->address_book->selects,
4434 (a_c_arg_t)dl->elnum)){
4435 DL_CACHE_S *dlc, dlc_restart;
4437 as.selections--;
4438 if(as.selections == 0)
4439 do_init_disp++;
4441 entry_unset_selected(pab->address_book->selects,
4442 (a_c_arg_t)dl->elnum);
4444 if(as.zoomed){
4445 dlc = get_dlc(as.top_ent+as.cur_row);
4446 if(as.selections){
4447 flush_dlc_from_cache(dlc);
4448 dlc = get_dlc(as.top_ent+as.cur_row);
4449 ps->mangled_body = 1;
4450 if(dlc->type == DlcEnd){
4451 r = prev_selectable_line(as.cur_row +
4452 as.top_ent,
4453 &new_line);
4454 if(r){
4455 as.cur_row = new_line - as.top_ent;
4456 if(as.cur_row < 0){
4457 as.top_ent += as.cur_row;
4458 as.cur_row = 0;
4459 start_disp = 0;
4461 else
4462 start_disp = as.cur_row;
4465 else
4466 start_disp = MAX(as.cur_row-1,0);
4468 else{
4469 dlc_restart = *dlc;
4470 as.zoomed = 0;
4471 q_status_message(SM_ORDER, 0, 2,
4472 _("Zoom Mode is now off, no entries selected"));
4474 warp_to_dlc(&dlc_restart, 0L);
4475 /* put current entry in middle of screen */
4476 as.top_ent =
4477 first_line(0L - (long)as.l_p_page/2L);
4478 as.cur_row = 0L - as.top_ent;
4479 ps->mangled_body = 1;
4480 start_disp = 0;
4483 else if(F_OFF(F_UNSELECT_WONT_ADVANCE,ps_global)){
4485 r = next_selectable_line(as.cur_row+as.top_ent,
4486 &new_line);
4487 if(r){
4489 for(ll = new_line;
4490 (dl=dlist(ll))->type != End;
4491 ll++)
4492 if(dl->type == ListHead || dl->type == Simple)
4493 break;
4495 if(dl->type != End)
4496 new_line = ll;
4498 as.cur_row = new_line - as.top_ent;
4499 if(as.cur_row >= as.l_p_page){
4500 /*-- Changed pages --*/
4501 as.top_ent += as.l_p_page;
4502 as.cur_row -= as.l_p_page;
4503 /* if it is still off screen */
4504 if(as.cur_row >= as.l_p_page){
4505 as.top_ent += (as.cur_row-as.l_p_page+1);
4506 as.cur_row = (as.l_p_page - 1);
4509 start_disp = 0;
4510 ps->mangled_body = 1;
4515 else{
4516 if(as.selections == 0)
4517 do_init_disp++;
4519 as.selections++;
4521 entry_set_selected(pab->address_book->selects,
4522 (a_c_arg_t)dl->elnum);
4523 r = next_selectable_line(as.cur_row+as.top_ent,
4524 &new_line);
4525 if(r){
4527 for(ll = new_line;
4528 (dl=dlist(ll))->type != End;
4529 ll++)
4530 if(dl->type == ListHead || dl->type == Simple)
4531 break;
4533 if(dl->type != End)
4534 new_line = ll;
4536 as.cur_row = new_line - as.top_ent;
4537 if(as.cur_row >= as.l_p_page){
4538 /*-- Changed pages --*/
4539 as.top_ent += as.l_p_page;
4540 as.cur_row -= as.l_p_page;
4541 /* if it is still off screen */
4542 if(as.cur_row >= as.l_p_page){
4543 as.top_ent += (as.cur_row-as.l_p_page+1);
4544 as.cur_row = (as.l_p_page - 1);
4547 start_disp = 0;
4548 ps->mangled_body = 1;
4554 * If we switch from selected to non-selected or
4555 * vice versa, we have to init_disp_form() for all
4556 * the addrbooks, in case we're using the X instead
4557 * of bold.
4559 if(do_init_disp){
4560 ps->mangled_footer = 1;
4561 for(i = 0; i < as.n_addrbk; i++)
4562 init_disp_form(&as.adrbks[i],
4563 ps->VAR_ABOOK_FORMATS, i);
4566 else
4567 q_status_message(SM_ORDER, 0, 4,
4568 _("You may not select list members, only whole lists or personal entries"));
4570 else
4571 q_status_message(SM_ORDER, 0, 4,
4572 _("You may only select addresses or lists"));
4574 break;
4577 /*--- Zoom in and look only at selected entries (or zoom out) --*/
4578 case MC_ZOOM:
4579 as.zoomed = (1 - as.zoomed);
4580 if(as.zoomed)
4581 ab_zoom((pab && pab->address_book) ? pab->address_book->selects
4582 : NULL,
4583 &start_disp);
4584 else{
4585 q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now off"));
4586 ab_unzoom(&start_disp);
4589 break;
4592 /*--- Apply a command -----------*/
4593 case MC_APPLY:
4594 if(as.selections){
4595 if(((ab_apply_cmd(ps, pab ? pab->address_book : NULL,
4596 as.top_ent+as.cur_row, command_line) &&
4597 F_ON(F_AUTO_UNZOOM, ps)) || !as.selections) && as.zoomed){
4599 ab_unzoom(NULL);
4600 ps_global->mangled_body = 1;
4604 * In case the line we're now at is not a selectable
4605 * field.
4607 * We set start_disp to zero here but rely on the called
4608 * routine to set mangled_body if appropriate.
4610 start_disp = 0;
4611 new_line = first_selectable_line(as.cur_row+as.top_ent);
4612 if(new_line != NO_LINE
4613 && new_line != as.cur_row+as.top_ent){
4614 as.cur_row = new_line - as.top_ent;
4615 if(as.cur_row < 0){
4616 as.top_ent += as.cur_row;
4617 as.cur_row = 0;
4619 else if(as.cur_row >= as.l_p_page){
4620 as.top_ent += (as.cur_row - as.l_p_page + 1);
4621 as.cur_row = as.l_p_page - 1;
4625 else
4626 q_status_message(SM_ORDER, 0, 2,
4627 _("No selected entries to apply command to"));
4629 break;
4632 /*--------- QUIT pine -----------*/
4633 case MC_QUIT:
4634 dprint((7, "Quitting pine from addrbook\n"));
4635 ps->next_screen = quit_screen;
4636 break;
4639 /*--------- Top of Folder list -----------*/
4640 case MC_COLLECTIONS:
4641 dprint((7, "Goto folder lister from addrbook\n"));
4642 ps->next_screen = folder_screen;
4643 break;
4646 /*---------- Open specific new folder ----------*/
4647 case MC_GOTO:
4648 dprint((7, "Goto from addrbook\n"));
4649 ab_goto_folder(command_line);
4650 break;
4653 /*--------- Index -----------*/
4654 case MC_INDEX:
4655 dprint((7, "Goto message index from addrbook\n"));
4656 if(THREADING()
4657 && sp_viewing_a_thread(ps->mail_stream)
4658 && unview_thread(ps, ps->mail_stream, ps->msgmap)){
4659 ps->next_screen = mail_index_screen;
4660 ps->view_skipped_index = 0;
4661 ps->mangled_screen = 1;
4664 ps->next_screen = mail_index_screen;
4665 break;
4668 /*----------------- Print --------------------*/
4669 case MC_PRINTTXT:
4670 (void)ab_print(0);
4671 ps->mangled_screen = 1;
4672 break;
4675 /*------ Copy entries into an abook ----*/
4676 case MC_SAVE:
4677 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4678 q_status_message(SM_ORDER, 0, 4, _("No entries to save"));
4679 break;
4682 if(entry_is_clickable(as.top_ent+as.cur_row)){
4683 clickable_warning(as.top_ent+as.cur_row);
4684 break;
4687 if(empty){
4688 empty_warning(as.top_ent+as.cur_row);
4689 break;
4692 (void)ab_save(ps, pab ? pab->address_book : NULL,
4693 as.top_ent+as.cur_row, command_line, 0);
4694 break;
4697 /*------ Forward an entry in mail -----------*/
4698 case MC_FORWARD:
4699 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4700 q_status_message(SM_ORDER, 0, 4, _("No entries to forward"));
4701 break;
4704 if(entry_is_clickable(as.top_ent+as.cur_row)){
4705 clickable_warning(as.top_ent+as.cur_row);
4706 break;
4709 if(empty){
4710 empty_warning(as.top_ent+as.cur_row);
4711 break;
4714 if(!is_addr(as.top_ent+as.cur_row)){
4715 q_status_message(SM_ORDER, 0, 4, _("Nothing to forward"));
4716 break;
4719 dl = dlist(as.top_ent+as.cur_row);
4720 if(dl->type != ListHead && dl->type != Simple){
4721 q_status_message(SM_ORDER, 0, 4,
4722 _("Can only forward whole entries"));
4723 break;
4726 (void)ab_forward(ps, as.top_ent+as.cur_row, 0);
4727 ps->mangled_footer = 1;
4728 break;
4731 case MC_REPAINT:
4732 /* ^L attempts to resynchronize with changed addrbooks */
4733 if(adrbk_check_all_validity_now())
4734 (void)resync_screen(pab, style, checkedn);
4736 /* fall through */
4738 case MC_RESIZE:
4739 mark_status_dirty();
4740 mark_titlebar_dirty();
4741 mark_keymenu_dirty();
4742 ClearBody();
4743 ps->mangled_screen = 1;
4744 if(c == KEY_RESIZE)
4745 ab_resize();
4747 break;
4750 case MC_UTF8:
4751 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps) ? "F1" : "?");
4752 break;
4755 /*------ Some back compatibility messages -----*/
4756 case MC_UNKNOWN:
4757 if(c == 'e' && !are_selecting){
4758 q_status_message(SM_ORDER | SM_DING, 0, 2,
4759 _("Command \"E\" not defined. Use \"View/Update\" to edit an entry"));
4760 break;
4762 else if(c == 's'
4763 && !(are_selecting || entry_is_clickable(as.top_ent+as.cur_row))){
4764 q_status_message(SM_ORDER | SM_DING, 0, 2,
4765 _("Command \"S\" not defined. Use \"AddNew\" to create a list"));
4766 break;
4768 else if(c == 'z' && !are_selecting){
4769 q_status_message(SM_ORDER | SM_DING, 0, 2,
4770 _("Command \"Z\" not defined. Use \"View/Update\" to add to a list"));
4771 break;
4773 /* else, fall through */
4775 default:
4776 bogus_command(c, F_ON(F_USE_FK, ps) ? "F1" : "?");
4777 break;
4780 if(ps->next_screen != SCREEN_FUN_NULL)
4781 quit++;
4784 erase_selections();
4785 return NULL;
4790 * Turn on zoom mode and zoom in if applicable.
4792 * Args selecteds -- tells which entries are selected in current abook
4793 * start_disp -- Passed in so we can set it back in the caller
4795 void
4796 ab_zoom(EXPANDED_S *selecteds, int *start_disp)
4798 AddrScrn_Disp *dl;
4799 DL_CACHE_S *dlc, dlc_restart;
4801 as.zoomed = 1;
4803 if(as.selections){
4804 q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now on"));
4805 if(cur_is_open()){
4806 dl = dlist(as.top_ent+as.cur_row);
4807 if((dl->type == ListHead ||
4808 dl->type == Simple ||
4809 dl->type == ListEmpty ||
4810 dl->type == ListClickHere ||
4811 dl->type == ListEnt) &&
4812 entry_is_selected(selecteds, (a_c_arg_t)dl->elnum)){
4813 dlc = get_dlc(as.top_ent+as.cur_row);
4814 dlc_restart = *dlc;
4815 warp_to_dlc(&dlc_restart, 0L);
4816 /* put current entry in middle of screen */
4817 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
4818 as.cur_row = 0L - as.top_ent;
4820 else{
4821 long new_ent;
4823 warp_to_top_of_abook(as.cur);
4824 as.top_ent = 0L;
4825 new_ent = first_selectable_line(0L);
4826 if(new_ent == NO_LINE)
4827 as.cur_row = 0L;
4828 else
4829 as.cur_row = new_ent;
4831 /* if it is off screen */
4832 if(as.cur_row >= as.l_p_page){
4833 as.top_ent += (as.cur_row - as.l_p_page + 1);
4834 as.cur_row = (as.l_p_page - 1);
4838 else{
4839 dlc = get_dlc(as.top_ent+as.cur_row);
4840 dlc_restart = *dlc;
4841 warp_to_dlc(&dlc_restart, 0L);
4842 /* put current entry in middle of screen */
4843 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
4844 as.cur_row = 0L - as.top_ent;
4847 ps_global->mangled_body = 1;
4848 *start_disp = 0;
4850 else{
4851 as.zoomed = 0;
4852 q_status_message(SM_ORDER, 0, 2, _("No selected entries to zoom on"));
4858 * Turn off zoom mode and zoom out if applicable.
4860 * Args start_disp -- Passed in so we can set it back in the caller
4862 void
4863 ab_unzoom(int *start_disp)
4865 DL_CACHE_S *dlc, dlc_restart;
4867 as.zoomed = 0;
4868 dlc = get_dlc(as.top_ent+as.cur_row);
4869 if(dlc->type == DlcZoomEmpty){
4870 long new_ent;
4872 warp_to_beginning();
4873 as.top_ent = 0L;
4874 new_ent = first_selectable_line(0L);
4875 if(new_ent == NO_LINE)
4876 as.cur_row = 0L;
4877 else
4878 as.cur_row = new_ent;
4880 /* if it is off screen */
4881 if(as.cur_row >= as.l_p_page){
4882 as.top_ent += (as.cur_row - as.l_p_page + 1);
4883 as.cur_row = (as.l_p_page - 1);
4886 else{
4887 dlc_restart = *dlc;
4888 warp_to_dlc(&dlc_restart, 0L);
4889 /* put current entry in middle of screen */
4890 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
4891 as.cur_row = 0L - as.top_ent;
4894 ps_global->mangled_body = 1;
4895 if(start_disp)
4896 *start_disp = 0;
4901 * Post an empty addrbook warning.
4903 * Args: cur_line -- The current line position (in global display list)
4904 * of cursor
4906 void
4907 empty_warning(long int cur_line)
4909 register AddrScrn_Disp *dl;
4911 dl = dlist(cur_line);
4912 if(dl->type == NoAbooks)
4913 q_status_message(SM_ORDER, 0, 4,
4914 _("No address books configured, use Setup"));
4915 else if(dl->type == Empty)
4916 q_status_message(SM_ORDER, 0, 4, _("Address Book is Empty"));
4917 else
4918 q_status_message(SM_ORDER, 0, 4, _("Distribution List is Empty"));
4923 * Tell user to click on this to expand.
4925 * Args: cur_line -- The current line position (in global display list)
4926 * of cursor
4928 void
4929 clickable_warning(long int cur_line)
4931 register AddrScrn_Disp *dl;
4933 dl = dlist(cur_line);
4934 if(dl->type == Title || dl->type == ClickHereCmb)
4935 q_status_message(SM_ORDER, 0, 4, _("Address Book not expanded, use \">\" to expand"));
4936 else
4937 q_status_message(SM_ORDER, 0, 4, _("Distribution List not expanded, use \">\" to expand"));
4942 * Post a no tabs warning.
4944 void
4945 no_tabs_warning(void)
4947 q_status_message(SM_ORDER, 0, 4, "Tabs not allowed in address book");
4952 * Prompt for command to apply to selected entries
4954 * Returns: 1 if the entries are successfully commanded.
4955 * 0 otherwise
4958 ab_apply_cmd(struct pine *ps, AdrBk *abook, long int cur_line, int command_line)
4960 int ret = 0;
4961 static ESCKEY_S opts[] = {
4962 {'c', 'c', "C", "ComposeTo"},
4963 {'d', 'd', "D", "Delete"},
4964 {'%', '%', "%", "Print"},
4965 {'f', 'f', "F", "Forward"},
4966 {'s', 's', "S", "Save"},
4967 {'#', '#', "#", "Role"},
4968 { 0, '%', "", ""},
4969 {-1, 0, NULL, NULL}};
4970 #define PHANTOM_PRINT 6
4972 dprint((7, "- ab_apply_cmd -\n"));
4975 opts[PHANTOM_PRINT].ch = (F_ON(F_ENABLE_PRYNT, ps_global)) ? 'y' : -1;
4977 switch(radio_buttons("APPLY command : ", command_line, opts, 'z', 'x',
4978 NO_HELP, RB_NORM)){
4979 case 'c':
4980 ret = ab_compose_to_addr(cur_line, 1, 0);
4981 break;
4983 case '#':
4984 ret = ab_compose_to_addr(cur_line, 1, 1);
4985 break;
4987 case 'd':
4988 ret = ab_agg_delete(ps, 1);
4989 break;
4991 case '%':
4992 ret = ab_print(1);
4993 break;
4995 case 'f':
4996 ret = ab_forward(ps, cur_line, 1);
4997 break;
4999 case 's':
5000 ret = ab_save(ps, abook, cur_line, command_line, 1);
5001 break;
5003 case 'x':
5004 cmd_cancelled("Apply command");
5005 break;
5007 case 'z':
5008 q_status_message(SM_INFO, 0, 2,
5009 "Cancelled, there is no default command");
5010 break;
5013 ps_global->mangled_footer = 1;
5015 return(ret);
5020 * Allow user to mark some entries "selected".
5022 void
5023 ab_select(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int *start_disp)
5025 static ESCKEY_S sel_opts1[] = {
5026 {'a', 'a', "A", "unselect All"},
5027 { 0 , 'c', "C", NULL},
5028 {'b', 'b', "B", "Broaden selctn"},
5029 {'n', 'n', "N", "Narrow selctn"},
5030 {'f', 'f', "F", "Flip selected"},
5031 {-1, 0, NULL, NULL}
5033 static char *sel_pmt1 = "ALTER selection : ";
5034 static ESCKEY_S sel_opts2[] = {
5035 {'a', 'a', "A", "select All"},
5036 {'c', 'c', "C", "select Cur"},
5037 {'t', 't', "T", "Text"},
5038 {'s', 's', "S", "Status"},
5039 {-1, 0, NULL, NULL}
5041 static char *sel_pmt2 = "SELECT criteria : ";
5042 ESCKEY_S *sel_opts;
5043 HelpType help = NO_HELP;
5044 adrbk_cntr_t num, ab_count;
5045 int q = 0, rv = 0, narrow = 0,
5046 do_flush = 0, do_warp = 0, prevsel,
5047 move_current = 0, do_beginning = 0;
5048 long new_ent;
5049 AddrScrn_Disp *dl;
5050 DL_CACHE_S *dlc, dlc_restart;
5052 dprint((5, "- ab_select -\n"));
5054 if(cur_is_open()){ /* select applies only to this addrbook */
5056 ps->mangled_footer = 1;
5057 prevsel = as.selections;
5058 dl = dlist(cur_line);
5059 sel_opts = sel_opts2;
5062 * If already some selected, ask how to alter that selection.
5064 if(as.selections){
5065 sel_opts += 2; /* don't offer all or current below */
5066 if(dl && (dl->type == ListHead || dl->type == Simple)){
5067 sel_opts1[1].label = entry_is_selected(abook->selects,
5068 (a_c_arg_t)dl->elnum)
5069 ? "unselect Cur"
5070 : "select Cur";
5071 sel_opts1[1].ch = 'c';
5073 else
5074 sel_opts1[1].ch = -2; /* don't offer this choice */
5076 switch(q = radio_buttons(sel_pmt1, command_line, sel_opts1,
5077 'a', 'x', help, RB_NORM)){
5078 case 'n': /* narrow selection */
5079 narrow++;
5080 case 'b': /* broaden selection */
5081 q = 0; /* but don't offer criteria prompt */
5082 break;
5084 case 'c': /* select or unselect current */
5085 case 'a': /* select or unselect all */
5086 case 'f': /* flip selections */
5087 case 'x': /* cancel */
5088 break;
5090 default:
5091 q_status_message(SM_ORDER | SM_DING, 3, 3,
5092 "Unsupported Select option");
5093 return;
5097 if(abook && dl &&
5098 (dl->type == ListHead || dl->type == Simple)){
5099 sel_opts1[1].label = entry_is_selected(abook->selects,
5100 (a_c_arg_t)dl->elnum)
5101 ? "unselect Cur"
5102 : "select Cur";
5103 sel_opts1[1].ch = 'c';
5105 else
5106 sel_opts1[1].ch = -2; /* don't offer this choice */
5108 if(!q)
5109 q = radio_buttons(sel_pmt2, command_line, sel_opts,
5110 'c', 'x', help, RB_NORM);
5112 *start_disp = 0;
5113 dlc = get_dlc(cur_line);
5114 dlc_restart = *dlc;
5115 ab_count = adrbk_count(abook);
5117 switch(q){
5118 case 'x': /* cancel */
5119 cmd_cancelled("Select command");
5120 break;
5122 case 'c': /* select/unselect current */
5123 ps->mangled_body = 1;
5124 if(entry_is_selected(abook->selects, (a_c_arg_t)dl->elnum)){
5125 entry_unset_selected(abook->selects, (a_c_arg_t)dl->elnum);
5126 as.selections--;
5128 if(as.selections == 0 && as.zoomed){
5129 as.zoomed = 0;
5130 q_status_message(SM_ORDER, 0, 2,
5131 "Zoom Mode is now off, no entries selected");
5132 do_warp++;
5134 else if(as.zoomed){
5135 move_current++;
5136 do_flush++;
5138 else
5139 do_flush++;
5141 else{
5142 entry_set_selected(abook->selects, (a_c_arg_t)dl->elnum);
5143 as.selections++;
5145 if(as.selections == 1 && !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
5146 as.zoomed = 1;
5147 as.top_ent = dlc_restart.global_row;
5148 as.cur_row = 0;
5149 do_warp++;
5151 else
5152 do_flush++;
5155 break;
5157 case 'a':
5158 ps->mangled_body = 1;
5159 if(any_selected(abook->selects)){ /* unselect all */
5160 for(num = 0; num < ab_count; num++){
5161 if(entry_is_selected(abook->selects, (a_c_arg_t)num)){
5162 as.selections--;
5163 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5167 if(as.selections == 0 && as.zoomed){
5168 as.zoomed = 0;
5169 q_status_message(SM_ORDER, 0, 2,
5170 "Zoom Mode is now off, all entries UNselected");
5171 do_warp++;
5173 else{
5174 char bb[100];
5176 snprintf(bb, sizeof(bb), "%s entries UNselected%s%s%s",
5177 comatose(prevsel-as.selections),
5178 as.selections ? ", still " : "",
5179 as.selections ? comatose(as.selections) : "",
5180 as.selections ? " selected in other addrbooks" : "");
5181 bb[sizeof(bb)-1] = '\0';
5182 q_status_message(SM_ORDER, 0, 2, bb);
5183 if(as.zoomed)
5184 do_beginning++;
5185 else
5186 do_flush++;
5189 else{ /* select all */
5190 for(num = 0; num < ab_count; num++){
5191 if(!entry_is_selected(abook->selects, (a_c_arg_t)num)){
5192 as.selections++;
5193 entry_set_selected(abook->selects, (a_c_arg_t)num);
5197 q_status_message1(SM_ORDER, 0, 2, "All %s entries selected",
5198 comatose(ab_count));
5199 if(prevsel == 0 && as.selections > 0 &&
5200 !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
5201 as.zoomed = 1;
5202 as.top_ent = dlc_restart.global_row - as.cur_row;
5203 do_warp++;
5205 else if(dlc_restart.type == DlcZoomEmpty &&
5206 as.selections > prevsel)
5207 do_beginning++;
5208 else if(as.zoomed)
5209 do_warp++;
5210 else
5211 do_flush++;
5214 break;
5216 case 'f': /* flip selections in this abook */
5217 ps->mangled_body = 1;
5218 for(num = 0; num < ab_count; num++){
5219 if(entry_is_selected(abook->selects, (a_c_arg_t)num)){
5220 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5221 as.selections--;
5223 else{
5224 entry_set_selected(abook->selects, (a_c_arg_t)num);
5225 as.selections++;
5229 if(as.zoomed){
5230 if(as.selections)
5231 do_beginning++;
5232 else{
5233 as.zoomed = 0;
5234 q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off");
5235 do_warp++;
5238 else
5239 do_warp++;
5241 q_status_message1(SM_ORDER, 0, 2, "%s entries now selected",
5242 comatose(as.selections));
5244 break;
5246 case 't':
5247 case 's':
5248 switch(q){
5249 case 't':
5250 rv = ab_select_text(abook, narrow);
5251 break;
5253 case 's':
5254 rv = ab_select_type(abook, narrow);
5255 break;
5258 if(!rv){
5259 ps->mangled_body = 1;
5260 if(dlc_restart.type == DlcZoomEmpty &&
5261 as.selections > prevsel)
5262 do_beginning++;
5263 else if(as.zoomed){
5264 if(as.selections == 0){
5265 as.zoomed = 0;
5266 q_status_message(SM_ORDER, 0, 2,
5267 "Zoom Mode is now off");
5268 do_warp++;
5270 else
5271 do_beginning++;
5273 else{
5274 if(prevsel == 0 && as.selections > 0 &&
5275 !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
5276 as.zoomed = 1;
5277 do_beginning++;
5279 else
5280 do_warp++;
5283 if(prevsel == as.selections && prevsel > 0){
5284 if(as.selections == 1)
5285 q_status_message(SM_ORDER, 0, 2,
5286 "No change resulted, 1 entry remains selected");
5287 else
5288 q_status_message1(SM_ORDER, 0, 2,
5289 "No change resulted, %s entries remain selected",
5290 comatose(as.selections));
5292 else if(prevsel == 0){
5293 if(as.selections == 1)
5294 q_status_message(SM_ORDER, 0, 2,
5295 "Select matched 1 entry");
5296 else if(as.selections > 1)
5297 q_status_message1(SM_ORDER, 0, 2,
5298 "Select matched %s entries",
5299 comatose(as.selections));
5300 else
5301 q_status_message(SM_ORDER, 0, 2,
5302 "Select failed! No entries selected");
5304 else if(as.selections == 0){
5305 if(prevsel == 1)
5306 q_status_message(SM_ORDER, 0, 2,
5307 "The single selected entry is UNselected");
5308 else
5309 q_status_message1(SM_ORDER, 0, 2,
5310 "All %s entries UNselected",
5311 comatose(prevsel));
5313 else if(narrow){
5314 if(as.selections == 1 && (prevsel-as.selections) == 1)
5315 q_status_message(SM_ORDER, 0, 2,
5316 "1 entry now selected, 1 entry was UNselected");
5317 else if(as.selections == 1)
5318 q_status_message1(SM_ORDER, 0, 2,
5319 "1 entry now selected, %s entries were UNselected",
5320 comatose(prevsel-as.selections));
5321 else if((prevsel-as.selections) == 1)
5322 q_status_message1(SM_ORDER, 0, 2,
5323 "%s entries now selected, 1 entry was UNselected",
5324 comatose(as.selections));
5325 else
5326 q_status_message2(SM_ORDER, 0, 2,
5327 "%s entries now selected, %s entries were UNselected",
5328 comatose(as.selections),
5329 comatose(prevsel-as.selections));
5331 else{
5332 if((as.selections-prevsel) == 1)
5333 q_status_message1(SM_ORDER, 0, 2,
5334 "1 new entry selected, %s entries now selected",
5335 comatose(as.selections));
5336 else if(as.selections == 1)
5337 q_status_message1(SM_ORDER, 0, 2,
5338 "%s new entries selected, 1 entry now selected",
5339 comatose(as.selections-prevsel));
5340 else
5341 q_status_message2(SM_ORDER, 0, 2,
5342 "%s new entries selected, %s entries now selected",
5343 comatose(as.selections-prevsel),
5344 comatose(as.selections));
5348 break;
5350 default :
5351 q_status_message(SM_ORDER | SM_DING, 3, 3,
5352 "Unsupported Select option");
5353 break;
5356 else{
5357 if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
5358 q_status_message(SM_ORDER | SM_DING, 3, 3,
5359 "Select is only available from within an expanded address book");
5360 else
5361 q_status_message(SM_ORDER | SM_DING, 3, 3,
5362 "Select is only available when viewing an individual address book");
5364 return;
5367 if(rv)
5368 return;
5370 if(do_beginning){
5371 warp_to_beginning(); /* just go to top */
5372 as.top_ent = 0L;
5373 new_ent = first_selectable_line(0L);
5374 if(new_ent == NO_LINE)
5375 as.cur_row = 0L;
5376 else
5377 as.cur_row = new_ent;
5379 /* if it is off screen */
5380 if(as.cur_row >= as.l_p_page){
5381 as.top_ent += (as.cur_row - as.l_p_page + 1);
5382 as.cur_row = (as.l_p_page - 1);
5385 else if(do_flush)
5386 flush_dlc_from_cache(&dlc_restart);
5387 else if(do_warp)
5388 warp_to_dlc(&dlc_restart, dlc_restart.global_row);
5390 if(move_current){
5391 dlc = get_dlc(cur_line);
5392 if(dlc->type == DlcEnd){
5393 as.cur_row--;
5394 if(as.cur_row < 0){
5395 as.top_ent += as.cur_row; /* plus a negative number */
5396 as.cur_row = 0;
5397 *start_disp = 0;
5399 else
5400 *start_disp = as.cur_row;
5403 else
5404 *start_disp = as.cur_row;
5410 * Selects based on whether an entry is a list or not.
5412 * Returns: 0 if search went ok
5413 * -1 if there was a problem
5416 ab_select_type(AdrBk *abook, int narrow)
5418 static ESCKEY_S ab_sel_type_opt[] = {
5419 {'s', 's', "S", "Simple"},
5420 {'l', 'l', "L", "List"},
5421 {-1, 0, NULL, NULL}
5423 static char *ab_sel_type = "Select Lists or Simples (non Lists) ? ";
5424 int type;
5425 adrbk_cntr_t num, ab_count;
5427 dprint((6, "- ab_select_type -\n"));
5429 if(!abook)
5430 return -1;
5432 switch(type = radio_buttons(ab_sel_type, -FOOTER_ROWS(ps_global),
5433 ab_sel_type_opt, 'l', 'x', NO_HELP, RB_NORM)){
5434 case 'l':
5435 break;
5437 case 's':
5438 break;
5440 case 'x':
5441 cmd_cancelled("Select");
5442 return -1;
5444 default:
5445 dprint((1,"\n - BOTCH: ab_select_type unknown option\n"));
5446 return -1;
5449 ab_count = adrbk_count(abook);
5450 for(num = 0; num < ab_count; num++){
5451 AdrBk_Entry *abe;
5452 int matched;
5455 * If it won't possibly change state, don't look at it.
5457 if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) ||
5458 (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num)))
5459 continue;
5461 abe = adrbk_get_ae(abook, (a_c_arg_t) num);
5462 matched = ((type == 's' && abe->tag == Single) ||
5463 (type == 'l' && abe->tag == List));
5465 if(narrow && !matched){
5466 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5467 as.selections--;
5469 else if(!narrow && matched){
5470 entry_set_selected(abook->selects, (a_c_arg_t)num);
5471 as.selections++;
5475 return 0;
5480 * Selects based on string matches in various addrbook fields.
5482 * Returns: 0 if search went ok
5483 * -1 if there was a problem
5486 ab_select_text(AdrBk *abook, int narrow)
5488 static ESCKEY_S ab_sel_text_opt[] = {
5489 {'n', 'n', "N", "Nickname"},
5490 {'a', 'a', "A", "All Text"},
5491 {'f', 'f', "F", "Fullname"},
5492 {'e', 'e', "E", "Email Addrs"},
5493 {'c', 'c', "C", "Comment"},
5494 {'z', 'z', "Z", "Fcc"},
5495 {-1, 0, NULL, NULL}
5497 static char *ab_sel_text =
5498 "Select based on Nickname, All text, Fullname, Addrs, Comment, or Fcc ? ";
5499 HelpType help = NO_HELP;
5500 int type, r;
5501 char sstring[80+1], prompt[80];
5502 adrbk_cntr_t num, ab_count;
5503 char *fmt = "String in \"%s\" to match : ";
5505 dprint((6, "- ab_select_text -\n"));
5507 if(!abook)
5508 return -1;
5510 switch(type = radio_buttons(ab_sel_text, -FOOTER_ROWS(ps_global),
5511 ab_sel_text_opt, 'a', 'x', NO_HELP, RB_NORM)){
5512 case 'n':
5513 snprintf(prompt, sizeof(prompt), fmt, "Nickname");
5514 break;
5515 case 'a':
5516 snprintf(prompt, sizeof(prompt), fmt, "All Text");
5517 break;
5518 case 'f':
5519 snprintf(prompt, sizeof(prompt), fmt, "Fullname");
5520 break;
5521 case 'e':
5522 snprintf(prompt, sizeof(prompt), fmt, "addresses");
5523 break;
5524 case 'c':
5525 snprintf(prompt, sizeof(prompt), fmt, "Comment");
5526 break;
5527 case 'z':
5528 snprintf(prompt, sizeof(prompt), fmt, "Fcc");
5529 break;
5530 case 'x':
5531 break;
5532 default:
5533 dprint((1,"\n - BOTCH: ab_select_text unknown option\n"));
5534 return -1;
5537 prompt[sizeof(prompt)-1] = '\0';
5539 sstring[0] = '\0';
5540 while(type != 'x'){
5541 int flags = OE_APPEND_CURRENT;
5543 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
5544 sizeof(sstring), prompt, NULL, help, &flags);
5545 switch(r){
5546 case 3: /* BUG, no help */
5547 case 4:
5548 continue;
5550 default:
5551 break;
5554 if(r == 1 || sstring[0] == '\0')
5555 r = 'x';
5557 break;
5560 if(type == 'x' || r == 'x'){
5561 cmd_cancelled("Select");
5562 return -1;
5565 ab_count = adrbk_count(abook);
5566 for(num = 0; num < ab_count; num++){
5567 AdrBk_Entry *abe;
5568 int matched;
5571 * If it won't possibly change state, don't look at it.
5573 if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) ||
5574 (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num)))
5575 continue;
5577 abe = adrbk_get_ae(abook, (a_c_arg_t) num);
5578 matched = match_check(abe, type, sstring);
5580 if(narrow && !matched){
5581 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5582 as.selections--;
5584 else if(!narrow && matched){
5585 entry_set_selected(abook->selects, (a_c_arg_t)num);
5586 as.selections++;
5590 return 0;
5595 * Returns: 1 if a match is found for the entry
5596 * 0 no match
5597 * -1 error
5600 match_check(AdrBk_Entry *abe, int type, char *string)
5602 static int err = -1;
5603 static int match = 1;
5604 static int nomatch = 0;
5605 unsigned int checks;
5606 #define CK_NICKNAME 0x01
5607 #define CK_FULLNAME 0x02
5608 #define CK_ADDRESSES 0x04
5609 #define CK_FCC 0x08
5610 #define CK_COMMENT 0x10
5611 #define CK_ALL 0x1f
5613 checks = 0;
5615 switch(type){
5616 case 'n': /* Nickname */
5617 checks |= CK_NICKNAME;
5618 break;
5620 case 'f': /* Fullname */
5621 checks |= CK_FULLNAME;
5622 break;
5624 case 'e': /* Addrs */
5625 checks |= CK_ADDRESSES;
5626 break;
5628 case 'a': /* All Text */
5629 checks |= CK_ALL;
5630 break;
5632 case 'z': /* Fcc */
5633 checks |= CK_FCC;
5634 break;
5636 case 'c': /* Comment */
5637 checks |= CK_COMMENT;
5638 break;
5640 default:
5641 q_status_message(SM_ORDER | SM_DING, 3, 3, "Unknown type");
5642 return(err);
5645 if(checks & CK_NICKNAME){
5646 if(abe && abe->nickname && srchstr(abe->nickname, string))
5647 return(match);
5650 if(checks & CK_FULLNAME){
5651 if(abe &&
5652 abe->fullname &&
5653 abe->fullname[0] &&
5654 srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5655 SIZEOF_20KBUF, abe->fullname),
5656 string))
5657 return(match);
5660 if(checks & CK_ADDRESSES){
5661 if(abe &&
5662 abe->tag == Single &&
5663 abe->addr.addr &&
5664 abe->addr.addr[0] &&
5665 srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5666 SIZEOF_20KBUF, abe->addr.addr),
5667 string))
5668 return(match);
5669 else if(abe &&
5670 abe->tag == List &&
5671 abe->addr.list){
5672 char **p;
5674 for(p = abe->addr.list; p != NULL && *p != NULL; p++){
5675 if(srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5676 SIZEOF_20KBUF, *p),
5677 string))
5678 return(match);
5683 if(checks & CK_FCC){
5684 if(abe &&
5685 abe->fcc &&
5686 abe->fcc[0] &&
5687 srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5688 SIZEOF_20KBUF, abe->fcc),
5689 string))
5690 return(match);
5693 if(checks & CK_COMMENT && abe && abe->extra && abe->extra[0]){
5694 size_t n, len;
5695 unsigned char *p, *tmp = NULL;
5696 int found_it = 0;
5698 if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){
5699 len = n+1;
5700 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
5702 else{
5703 len = SIZEOF_20KBUF;
5704 p = (unsigned char *)tmp_20k_buf;
5707 if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra), string))
5708 found_it++;
5710 if(tmp)
5711 fs_give((void **)&tmp);
5713 if(found_it)
5714 return(match);
5717 return(nomatch);
5722 * Go to folder.
5724 * command_line -- The screen line on which to prompt
5726 void
5727 ab_goto_folder(int command_line)
5729 char *go_folder;
5730 CONTEXT_S *tc;
5731 int notrealinbox = 0;
5733 dprint((2, "- ab_goto_folder -\n"));
5735 tc = ps_global->context_current;
5737 go_folder = broach_folder(command_line, 1, &notrealinbox, &tc);
5739 if(go_folder != NULL)
5740 visit_folder(ps_global, go_folder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
5745 * Execute whereis command.
5747 * Returns value of the new top entry, or NO_LINE if cancelled.
5749 long
5750 ab_whereis(int *warped, int command_line)
5752 int rc, wrapped = 0;
5753 long new_top_ent, new_line;
5755 dprint((5, "- ab_whereis -\n"));
5757 rc = search_book(as.top_ent+as.cur_row, command_line,
5758 &new_line, &wrapped, warped);
5760 new_top_ent = NO_LINE;
5762 if(rc == -2)
5763 q_status_message(SM_INFO, 0, 2, _("Address book search cancelled"));
5765 else if(rc == -1)
5766 q_status_message(SM_ORDER, 0, 4, _("Word not found"));
5768 else if(rc == 0){ /* search succeeded */
5770 if(wrapped == 1)
5771 q_status_message(SM_INFO, 0, 2, _("Search wrapped to beginning"));
5772 else if(wrapped == 2)
5773 q_status_message(SM_INFO, 0, 2,
5774 _("Current line contains the only match"));
5776 /* know match is on the same page */
5777 if(!*warped &&
5778 new_line >= as.top_ent &&
5779 new_line < as.top_ent+as.l_p_page)
5780 new_top_ent = as.top_ent;
5781 /* don't know whether it is or not, reset top_ent */
5782 else
5783 new_top_ent = first_line(new_line - as.l_p_page/2);
5785 as.cur_row = new_line - new_top_ent;
5788 return(new_top_ent);
5793 * recalculate display parameters for window size change
5795 void
5796 ab_resize(void)
5798 long new_line;
5799 int old_l_p_p;
5800 DL_CACHE_S dlc_buf, *dlc_restart;
5802 old_l_p_p = as.l_p_page;
5803 as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)
5804 - HEADER_ROWS(ps_global);
5806 dprint((9, "- ab_resize -\n l_p_p was %d, now %d\n",
5807 old_l_p_p, as.l_p_page));
5809 if(as.l_p_page <= 0){
5810 as.no_op_possbl++;
5811 return;
5813 else
5814 as.no_op_possbl = 0;
5816 new_line = as.top_ent + as.cur_row;
5817 as.top_ent = first_line(new_line - as.l_p_page/2);
5818 as.cur_row = new_line - as.top_ent;
5819 as.old_cur_row = as.cur_row;
5821 /* need this to re-initialize Text and Title lines in display */
5822 /* get the old current line (which may be the wrong width) */
5823 dlc_restart = get_dlc(new_line);
5824 /* flush it from cache */
5825 flush_dlc_from_cache(dlc_restart);
5826 /* re-get it (should be right now) */
5827 dlc_restart = get_dlc(new_line);
5828 /* copy it to local storage */
5829 dlc_buf = *dlc_restart;
5830 dlc_restart = &dlc_buf;
5831 /* flush everything from cache and add that one line back in */
5832 warp_to_dlc(dlc_restart, new_line);
5837 * Returns 0 if we know for sure that there are no
5838 * addresses available in any of the addressbooks.
5840 * Easiest would be to start at 0 and go through the addrbook, but that will
5841 * be very slow for big addrbooks if we're not close to 0 already. Instead,
5842 * starting_hint is a hint at a good place to start looking.
5845 any_addrs_avail(long int starting_hint)
5847 register AddrScrn_Disp *dl;
5848 long lineno;
5851 * Look from lineno backwards first, in hopes of finding it in cache.
5853 lineno = starting_hint;
5854 for(dl=dlist(lineno);
5855 dl->type != Beginning;
5856 dl = dlist(--lineno)){
5857 if(dl->type == NoAbooks)
5858 return 0;
5860 switch(dl->type){
5861 case Simple:
5862 case ListEnt:
5863 case ListHead:
5864 case ZoomEmpty:
5865 case Title:
5866 case ListClickHere:
5867 case ClickHereCmb:
5868 return 1;
5869 default:
5870 break;
5874 /* search from here forward if we still don't know */
5875 lineno = starting_hint;
5876 for(dl=dlist(lineno);
5877 dl->type != End;
5878 dl = dlist(++lineno)){
5879 if(dl->type == NoAbooks)
5880 return 0;
5882 switch(dl->type){
5883 case Simple:
5884 case ListEnt:
5885 case ListHead:
5886 case ZoomEmpty:
5887 case Title:
5888 case ListClickHere:
5889 case ClickHereCmb:
5890 return 1;
5891 default:
5892 break;
5896 return 0;
5901 * Returns 1 if this line is a clickable line.
5904 entry_is_clickable(long int lineno)
5906 register AddrScrn_Disp *dl;
5908 if((dl = dlist(lineno)) &&
5909 (dl->type == Title || dl->type == ListClickHere ||
5910 dl->type == ClickHereCmb))
5911 return 1;
5913 return 0;
5918 * Returns 1 if this line is a clickable Title line.
5921 entry_is_clickable_title(long int lineno)
5923 register AddrScrn_Disp *dl;
5925 if((dl = dlist(lineno)) && (dl->type == Title || dl->type == ClickHereCmb))
5926 return 1;
5928 return 0;
5933 * Returns 1 if an address or list is selected.
5936 is_addr(long int lineno)
5938 register AddrScrn_Disp *dl;
5940 if((dl = dlist(lineno)) && (dl->type == ListHead ||
5941 dl->type == ListEnt ||
5942 dl->type == Simple))
5943 return 1;
5945 return 0;
5950 * Returns 1 if type of line is Empty.
5953 is_empty(long int lineno)
5955 register AddrScrn_Disp *dl;
5957 if((dl = dlist(lineno)) &&
5958 (dl->type == Empty || dl->type == ListEmpty || dl->type == ZoomEmpty))
5959 return 1;
5961 return 0;
5966 * Returns 1 if lineno is a list entry
5969 entry_is_listent(long int lineno)
5971 register AddrScrn_Disp *dl;
5973 if((dl = dlist(lineno)) && dl->type == ListEnt)
5974 return 1;
5976 return 0;
5981 * Returns 1 if lineno is a fake addrbook for config screen add
5982 * or if it is an AskServer line for LDAP query
5985 entry_is_addkey(long int lineno)
5987 register AddrScrn_Disp *dl;
5989 if((dl = dlist(lineno)) && (dl->type == AddFirstPers ||
5990 dl->type == AddFirstGlob ||
5991 dl->type == AskServer))
5992 return 1;
5994 return 0;
5999 * Returns 1 if lineno is the line to ask for directory server query
6002 entry_is_askserver(long int lineno)
6004 register AddrScrn_Disp *dl;
6006 if((dl = dlist(lineno)) && dl->type == AskServer)
6007 return 1;
6009 return 0;
6014 * Returns 1 if an add abook here would be global, not personal
6017 add_is_global(long int lineno)
6019 register AddrScrn_Disp *dl;
6021 dl = dlist(lineno);
6023 if(dl){
6024 if(dl->type == Title){
6025 register PerAddrBook *pab;
6027 pab = &as.adrbks[as.cur];
6028 if(pab && pab->type & GLOBAL)
6029 return 1;
6031 else if(dl->type == AddFirstGlob)
6032 return 1;
6035 return 0;
6040 * Find the first line greater than or equal to line. (Any line, not
6041 * necessarily selectable.)
6043 * Returns the line number of the found line or NO_LINE if there is none.
6045 * Warning: This just starts at the passed in line and goes forward until
6046 * it runs into a line that isn't a Beginning line. If the line passed in
6047 * is not in the dlc cache, it will have no way to know when it gets to the
6048 * real beginning.
6050 long
6051 first_line(long int line)
6053 long lineno;
6054 register PerAddrBook *pab;
6055 int i;
6057 for(lineno=line;
6058 dlist(lineno)->type == Beginning;
6059 lineno++)
6060 ;/* do nothing */
6062 if(dlist(lineno)->type != End)
6063 return(lineno);
6064 else{
6065 for(i = 0; i < as.n_addrbk; i++){
6066 pab = &as.adrbks[i];
6067 if(pab->ostatus != Open &&
6068 pab->ostatus != HalfOpen &&
6069 pab->ostatus != ThreeQuartOpen)
6070 return NO_LINE;
6073 as.no_op_possbl++;
6074 return(NO_LINE);
6080 * Find the line number of the next selectable line.
6082 * Args: cur_line -- The current line position (in global display list)
6083 * of cursor
6084 * new_line -- Return value: new line position
6086 * Result: The new line number is set.
6087 * The value 1 is returned if OK or 0 if there is no next line.
6090 next_selectable_line(long int cur_line, long int *new_line)
6092 /* skip over non-selectable lines */
6093 for(cur_line++;
6094 !line_is_selectable(cur_line) && dlist(cur_line)->type != End;
6095 cur_line++)
6096 ;/* do nothing */
6098 if(dlist(cur_line)->type == End)
6099 return 0;
6101 *new_line = cur_line;
6102 return 1;
6107 * Find the line number of the previous selectable line.
6109 * Args: cur_line -- The current line position (in global display list)
6110 * of cursor
6111 * new_line -- Return value: new line position
6113 * Result: The new line number is set.
6114 * The value 1 is returned if OK or 0 if there is no previous line.
6117 prev_selectable_line(long int cur_line, long int *new_line)
6119 /* skip backwards over non-selectable lines */
6120 for(cur_line--;
6121 !line_is_selectable(cur_line) && dlist(cur_line)->type != Beginning;
6122 cur_line--)
6123 ;/* do nothing */
6125 if(dlist(cur_line)->type == Beginning)
6126 return 0;
6128 *new_line = cur_line;
6130 return 1;
6135 * Resync the display with the addrbooks, which were just discovered
6136 * to be out of sync with the display.
6138 * Returns 1 -- current address book had to be resynced
6139 * 0 -- current address book not resynced
6142 resync_screen(PerAddrBook *pab, AddrBookArg style, int checkedn)
6144 AddrScrn_Disp *dl;
6145 int current_resynced = 0;
6146 DL_CACHE_S dlc_restart, *dlc = NULL;
6149 * The test below gives conditions under which it is safe to go ahead
6150 * and resync all the addrbooks that are out of sync now, and the
6151 * display won't change. Otherwise, we have to be careful to preserve
6152 * some of our state so that we can attempt to restore the screen to
6153 * a state that is as close as possible to what we have now. If the
6154 * currently opened address book (pab) is out of date we will lose
6155 * the expanded state of its distribution lists, which is no big deal.
6156 * Since resyncing also loses the checked status if we're selecting with
6157 * ListMode, we don't even attempt it in that case.
6159 if((ab_nesting_level < 2 && !cur_is_open() && checkedn == 0) ||
6160 (style == AddrBookScreen &&
6161 pab &&
6162 pab->address_book &&
6163 !(pab->address_book->flags & FILE_OUTOFDATE ||
6164 (pab->address_book->rd &&
6165 pab->address_book->rd->flags & REM_OUTOFDATE)))){
6167 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
6168 dlc = get_dlc(as.top_ent+as.cur_row);
6169 dlc_restart = *dlc;
6172 if(adrbk_check_and_fix_all(1, 0, 1)){
6173 ps_global->mangled_footer = 1; /* why? */
6174 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
6175 warp_to_dlc(&dlc_restart, 0L);
6176 /* put current entry in middle of screen */
6177 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
6178 as.cur_row = 0L - as.top_ent;
6179 ps_global->mangled_screen = 1;
6183 else if(style == AddrBookScreen){
6184 char *savenick = NULL;
6185 AdrBk_Entry *abe;
6186 adrbk_cntr_t old_entry_num, new_entry_num;
6187 long old_global_row;
6189 current_resynced++;
6192 * We're going to try to get the nickname of the current
6193 * entry and find it again after the resync.
6195 dl = dlist(as.top_ent+as.cur_row);
6196 if(dl->type == ListEnt ||
6197 dl->type == ListEmpty ||
6198 dl->type == ListClickHere ||
6199 dl->type == ListHead ||
6200 dl->type == Simple){
6201 abe = ae(as.top_ent+as.cur_row);
6202 old_entry_num = dl->elnum;
6203 old_global_row = as.top_ent+as.cur_row;
6204 if(abe && abe->nickname && abe->nickname[0])
6205 savenick = cpystr(abe->nickname);
6208 /* this will close and re-open current addrbook */
6209 (void)adrbk_check_and_fix_all(1, 0, 1);
6211 abe = NULL;
6212 if(savenick){
6213 abe = adrbk_lookup_by_nick(pab->address_book, savenick,
6214 &new_entry_num);
6215 fs_give((void **)&savenick);
6218 if(abe){ /* If we found the same nickname, move to it */
6219 dlc_restart.adrbk_num = as.cur;
6220 dlc_restart.dlcelnum = new_entry_num;
6221 dlc_restart.type = (abe->tag == Single)
6222 ? DlcSimple : DlcListHead;
6223 if(old_entry_num == new_entry_num)
6224 warp_to_dlc(&dlc_restart, old_global_row);
6225 else
6226 warp_to_dlc(&dlc_restart, 0L);
6228 else
6229 warp_to_top_of_abook(as.cur);
6231 if(!abe || old_entry_num != new_entry_num){
6232 /* put current entry in middle of screen */
6233 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
6234 as.cur_row = 0L - as.top_ent;
6238 return(current_resynced);
6243 * Erase all the check marks.
6245 void
6246 erase_checks(void)
6248 int i;
6249 PerAddrBook *pab;
6251 for(i = 0; i < as.n_addrbk; i++){
6252 pab = &as.adrbks[i];
6253 if(pab->address_book && pab->address_book->checks)
6254 exp_free(pab->address_book->checks);
6256 init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
6262 * Erase all the selections.
6264 void
6265 erase_selections(void)
6267 int i;
6268 PerAddrBook *pab;
6270 for(i = 0; i < as.n_addrbk; i++){
6271 pab = &as.adrbks[i];
6272 if(pab->address_book && pab->address_book->selects)
6273 exp_free(pab->address_book->selects);
6276 for(i = 0; i < as.n_addrbk; i++){
6277 pab = &as.adrbks[i];
6278 init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
6281 as.selections = 0;
6286 * return values of search_in_one_line are or'd combination of these
6288 #define MATCH_NICK 0x1 /* match in field 0 */
6289 #define MATCH_FULL 0x2 /* match in field 1 */
6290 #define MATCH_ADDR 0x4 /* match in field 2 */
6291 #define MATCH_FCC 0x8 /* match in fcc field */
6292 #define MATCH_COMMENT 0x10 /* match in comment field */
6293 #define MATCH_BIGFIELD 0x20 /* match in one of the fields that crosses the
6294 whole screen, like a Title field */
6295 #define MATCH_LISTMEM 0x40 /* match list member */
6297 * Prompt user for search string and call find_in_book.
6299 * Args: cur_line -- The current line position (in global display list)
6300 * of cursor
6301 * command_line -- The screen line to prompt on
6302 * new_line -- Return value: new line position
6303 * wrapped -- Wrapped to beginning of display, tell user
6304 * warped -- Warped to a new location in the addrbook
6306 * Result: The new line number is set if the search is successful.
6307 * Returns 0 if found, -1 if not, -2 if cancelled.
6311 search_book(long int cur_line, int command_line, long int *new_line, int *wrapped, int *warped)
6313 int i=0, find_result, rc, flags, ku;
6314 static HISTORY_S *history = NULL;
6315 char search_string[MAX_SEARCH + 1];
6316 char prompt[MAX_SEARCH + 50], nsearch_string[MAX_SEARCH+1], *p;
6317 HelpType help;
6318 ESCKEY_S ekey[6];
6319 PerAddrBook *pab;
6320 long nl;
6322 dprint((7, "- search_book -\n"));
6324 init_hist(&history, HISTSIZE);
6326 search_string[0] = '\0';
6327 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
6328 strncpy(search_string, p, sizeof(search_string));
6329 search_string[sizeof(search_string)-1] = '\0';
6332 snprintf(prompt, sizeof(prompt), _("Word to search for [%.*s]: "), MAX_SEARCH, search_string);
6333 prompt[sizeof(prompt)-1] = '\0';
6334 help = NO_HELP;
6335 nsearch_string[0] = '\0';
6337 ekey[i].ch = 0;
6338 ekey[i].rval = 0;
6339 ekey[i].name = "";
6340 ekey[i++].label = "";
6342 ekey[i].ch = ctrl('Y');
6343 ekey[i].rval = 10;
6344 ekey[i].name = "^Y";
6345 /* TRANSLATORS: User is searching in address book. One of the options is to
6346 search for the First Address */
6347 ekey[i++].label = _("First Adr");
6349 ekey[i].ch = ctrl('V');
6350 ekey[i].rval = 11;
6351 ekey[i].name = "^V";
6352 /* TRANSLATORS: Last Address */
6353 ekey[i++].label = _("Last Adr");
6355 ekey[i].ch = KEY_UP;
6356 ekey[i].rval = 30;
6357 ekey[i].name = "";
6358 ku = i;
6359 ekey[i++].label = "";
6361 ekey[i].ch = KEY_DOWN;
6362 ekey[i].rval = 31;
6363 ekey[i].name = "";
6364 ekey[i++].label = "";
6366 ekey[i].ch = -1;
6368 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
6369 while(1){
6372 * 2 is really 1 because there will be one real entry and
6373 * one entry of "" because of the get_prev_hist above.
6375 if(items_in_hist(history) > 2){
6376 ekey[ku].name = HISTORY_UP_KEYNAME;
6377 ekey[ku].label = HISTORY_KEYLABEL;
6378 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
6379 ekey[ku+1].label = HISTORY_KEYLABEL;
6381 else{
6382 ekey[ku].name = "";
6383 ekey[ku].label = "";
6384 ekey[ku+1].name = "";
6385 ekey[ku+1].label = "";
6388 rc = optionally_enter(nsearch_string, command_line, 0,
6389 sizeof(nsearch_string),
6390 prompt, ekey, help, &flags);
6391 if(rc == 3){
6392 help = help == NO_HELP ? h_oe_searchab : NO_HELP;
6393 continue;
6395 else if(rc == 10){
6396 *warped = 1;
6397 warp_to_beginning(); /* go to top of addrbooks */
6398 if((nl=first_selectable_line(0L)) != NO_LINE){
6399 *new_line = nl;
6400 q_status_message(SM_INFO, 0, 2, _("Searched to first entry"));
6401 return 0;
6403 else{
6404 q_status_message(SM_INFO, 0, 2, _("No entries"));
6405 return -1;
6408 else if(rc == 11){
6409 *warped = 1;
6410 warp_to_end(); /* go to bottom */
6411 if((nl=first_selectable_line(0L)) != NO_LINE){
6412 *new_line = nl;
6413 q_status_message(SM_INFO, 0, 2, _("Searched to last entry"));
6414 return 0;
6416 else{
6417 q_status_message(SM_INFO, 0, 2, _("No entries"));
6418 return -1;
6421 else if(rc == 30){
6422 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
6423 strncpy(nsearch_string, p, sizeof(nsearch_string));
6424 nsearch_string[sizeof(nsearch_string)-1] = '\0';
6426 else
6427 Writechar(BELL, 0);
6429 continue;
6431 else if(rc == 31){
6432 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
6433 strncpy(nsearch_string, p, sizeof(nsearch_string));
6434 nsearch_string[sizeof(nsearch_string)-1] = '\0';
6436 else
6437 Writechar(BELL, 0);
6439 continue;
6442 if(rc != 4){ /* 4 is redraw */
6443 save_hist(history, nsearch_string, 0, NULL);
6444 break;
6449 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
6450 return -2;
6452 if(nsearch_string[0] != '\0'){
6453 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
6454 search_string[sizeof(search_string)-1] = '\0';
6457 find_result = find_in_book(cur_line, search_string, new_line, wrapped);
6459 if(*wrapped == 1)
6460 *warped = 1;
6462 if(find_result){
6463 int also = 0, notdisplayed = 0;
6465 pab = &as.adrbks[adrbk_num_from_lineno(*new_line)];
6466 if(find_result & MATCH_NICK){
6467 if(pab->nick_is_displayed)
6468 also++;
6469 else
6470 notdisplayed++;
6473 if(find_result & MATCH_FULL){
6474 if(pab->full_is_displayed)
6475 also++;
6476 else
6477 notdisplayed++;
6480 if(find_result & MATCH_ADDR){
6481 if(pab->addr_is_displayed)
6482 also++;
6483 else
6484 notdisplayed++;
6487 if(find_result & MATCH_FCC){
6488 if(pab->fcc_is_displayed)
6489 also++;
6490 else
6491 notdisplayed++;
6494 if(find_result & MATCH_COMMENT){
6495 if(pab->comment_is_displayed)
6496 also++;
6497 else
6498 notdisplayed++;
6501 if(find_result & MATCH_LISTMEM){
6502 AddrScrn_Disp *dl;
6504 dl = dlist(*new_line);
6505 if(F_OFF(F_EXPANDED_DISTLISTS,ps_global)
6506 && !exp_is_expanded(pab->address_book->exp, (a_c_arg_t)dl->elnum))
6507 notdisplayed++;
6510 if(notdisplayed > 1 && *wrapped == 0){
6511 if(also)
6512 /* TRANSLATORS: These "matched" messages are advisory messages explaining
6513 how a search command matched an entry */
6514 q_status_message1(SM_ORDER,0,4, _("Also matched string in %s other fields"),
6515 comatose(notdisplayed));
6516 else
6517 q_status_message1(SM_ORDER,0,4, _("Matched string in %s fields"),
6518 comatose(notdisplayed));
6520 else if(notdisplayed == 1 && *wrapped == 0){
6521 if(also){
6522 if(find_result & MATCH_NICK && !pab->nick_is_displayed)
6523 q_status_message(SM_ORDER,0,4, _("Also matched string in Nickname field"));
6524 else if(find_result & MATCH_FULL && !pab->full_is_displayed)
6525 q_status_message(SM_ORDER,0,4, _("Also matched string in Fullname field"));
6526 else if(find_result & MATCH_ADDR && !pab->addr_is_displayed)
6527 q_status_message(SM_ORDER,0,4, _("Also matched string in Address field"));
6528 else if(find_result & MATCH_FCC && !pab->fcc_is_displayed)
6529 q_status_message(SM_ORDER,0,4, _("Also matched string in Fcc field"));
6530 else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed)
6531 q_status_message(SM_ORDER,0,4, _("Also matched string in Comment field"));
6532 else if(find_result & MATCH_LISTMEM)
6533 q_status_message(SM_ORDER,0,4, _("Also matched string in list member address"));
6534 else
6535 q_status_message(SM_ORDER,0,4, _("Also matched string in ?"));
6537 else{
6538 if(find_result & MATCH_NICK && !pab->nick_is_displayed)
6539 q_status_message(SM_ORDER,0,4, _("Matched string in Nickname field"));
6540 else if(find_result & MATCH_FULL && !pab->full_is_displayed)
6541 q_status_message(SM_ORDER,0,4, _("Matched string in Fullname field"));
6542 else if(find_result & MATCH_ADDR && !pab->addr_is_displayed)
6543 q_status_message(SM_ORDER,0,4, _("Matched string in Address field"));
6544 else if(find_result & MATCH_FCC && !pab->fcc_is_displayed)
6545 q_status_message(SM_ORDER,0,4, _("Matched string in Fcc field"));
6546 else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed)
6547 q_status_message(SM_ORDER,0,4, _("Matched string in Comment field"));
6548 else if(find_result & MATCH_LISTMEM)
6549 q_status_message(SM_ORDER,0,4, _("Matched string in list member address"));
6550 else
6551 q_status_message(SM_ORDER,0,4, _("Matched string in ?"));
6556 /* be sure to be on a selectable field */
6557 if(!line_is_selectable(*new_line))
6558 if((nl=first_selectable_line(*new_line+1)) != NO_LINE)
6559 *new_line = nl;
6562 return(find_result ? 0 : -1);
6567 * Search the display list for the given string.
6569 * Args: cur_line -- The current line position (in global display list)
6570 * of cursor
6571 * string -- String to search for
6572 * new_line -- Return value: new line position
6573 * wrapped -- Wrapped to beginning of display during search
6575 * Result: The new line number is set if the search is successful.
6576 * Returns 0 -- string not found
6577 * Otherwise, a bitmask of which fields the string was found in.
6580 find_in_book(long int cur_line, char *string, long int *new_line, int *wrapped)
6582 register AddrScrn_Disp *dl;
6583 long nl, nl_save;
6584 int fields;
6585 AdrBk_Entry *abe;
6586 char *listaddr = NULL;
6587 DL_CACHE_S *dlc,
6588 dlc_save; /* a local copy */
6591 dprint((9, "- find_in_book -\n"));
6594 * Save info to allow us to get back to where we were if we can't find
6595 * the string. Also used to stop our search if we wrap back to the
6596 * start and search forward.
6599 nl_save = cur_line;
6600 dlc = get_dlc(nl_save);
6601 dlc_save = *dlc;
6603 *wrapped = 0;
6604 nl = cur_line + 1L;
6606 /* start with next line and search to the end of the disp_list */
6607 dl = dlist(nl);
6608 while(dl->type != End){
6609 if(dl->type == Simple ||
6610 dl->type == ListHead ||
6611 dl->type == ListEnt ||
6612 dl->type == ListClickHere){
6613 abe = ae(nl);
6614 if(dl->type == ListEnt)
6615 listaddr = listmem(nl);
6617 else
6618 abe = (AdrBk_Entry *)NULL;
6620 if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0)
6621 goto found;
6623 dl = dlist(++nl);
6628 * Wrap back to the start of the addressbook and search forward
6629 * from there.
6631 warp_to_beginning(); /* go to top of addrbooks */
6632 nl = 0L; /* line number is always 0 after warp_to_beginning */
6633 *wrapped = 1;
6635 dlc = get_dlc(nl);
6636 while(!matching_dlcs(&dlc_save, dlc) && dlc->type != DlcEnd){
6638 fill_in_dl_field(dlc);
6639 dl = &dlc->dl;
6641 if(dl->type == Simple ||
6642 dl->type == ListHead ||
6643 dl->type == ListEnt ||
6644 dl->type == ListClickHere){
6645 abe = ae(nl);
6646 if(dl->type == ListEnt)
6647 listaddr = listmem(nl);
6649 else
6650 abe = (AdrBk_Entry *)NULL;
6652 if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0)
6653 goto found;
6655 dlc = get_dlc(++nl);
6658 /* see if it is in the current line */
6659 fill_in_dl_field(dlc);
6660 dl = &dlc->dl;
6662 if(dl->type == Simple ||
6663 dl->type == ListHead ||
6664 dl->type == ListEnt ||
6665 dl->type == ListClickHere){
6666 abe = ae(nl);
6667 if(dl->type == ListEnt)
6668 listaddr = listmem(nl);
6670 else
6671 abe = (AdrBk_Entry *)NULL;
6673 fields = search_in_one_line(dl, abe, listaddr, string);
6674 if(dl->usst &&
6675 (dl->type == Text || dl->type == Title || dl->type == TitleCmb))
6676 fs_give((void **)&dl->usst);
6678 /* jump cache back to where we started */
6679 *wrapped = 0;
6680 warp_to_dlc(&dlc_save, nl_save);
6681 if(fields){
6682 *new_line = nl_save; /* because it was in current line */
6683 *wrapped = 2;
6686 nl = *new_line;
6688 found:
6689 *new_line = nl;
6690 return(fields);
6695 * Look in line dl for string.
6697 * Args: dl -- the display list for this line
6698 * abe -- AdrBk_Entry if it is an address type
6699 * listaddr -- list member if it is of type ListEnt
6700 * string -- look for this string
6702 * Result: 0 -- string not found
6703 * Otherwise, a bitmask of which fields the string was found in.
6704 * MATCH_NICK 0x1
6705 * MATCH_FULL 0x2
6706 * MATCH_ADDR 0x4
6707 * MATCH_FCC 0x8
6708 * MATCH_COMMENT 0x10
6709 * MATCH_BIGFIELD 0x20
6710 * MATCH_LISTMEM 0x40
6713 search_in_one_line(AddrScrn_Disp *dl, AdrBk_Entry *abe, char *listaddr, char *string)
6715 register int c;
6716 int ret_val = 0;
6717 char **lm;
6719 for(c = 0; c < 5; c++){
6720 switch(c){
6721 case 0:
6722 switch(dl->type){
6723 case Simple:
6724 case ListHead:
6725 if(srchstr(abe->nickname, string))
6726 ret_val |= MATCH_NICK;
6728 break;
6730 case Text:
6731 case Title:
6732 case TitleCmb:
6733 case AskServer:
6734 if(srchstr(dl->usst, string))
6735 ret_val |= MATCH_BIGFIELD;
6737 default:
6738 break;
6740 break;
6742 case 1:
6743 switch(dl->type){
6744 case Simple:
6745 case ListHead:
6746 if(abe && srchstr(
6747 (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
6748 SIZEOF_20KBUF, abe->fullname),
6749 string))
6750 ret_val |= MATCH_FULL;
6752 default:
6753 break;
6755 break;
6757 case 2:
6758 switch(dl->type){
6759 case Simple:
6760 if(srchstr((abe && abe->tag == Single) ?
6761 abe->addr.addr : NULL, string))
6762 ret_val |= MATCH_ADDR;
6764 break;
6766 case ListEnt:
6767 if(srchstr(
6768 (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
6769 SIZEOF_20KBUF, listaddr), string))
6770 ret_val |= MATCH_LISTMEM;
6772 break;
6774 case ListClickHere:
6775 if(abe)
6776 for(lm = abe->addr.list;
6777 !(ret_val & MATCH_LISTMEM) && *lm; lm++)
6778 if(srchstr(*lm, string))
6779 ret_val |= MATCH_LISTMEM;
6781 break;
6783 case Empty:
6784 case ListEmpty:
6785 if(srchstr(EMPTY, string))
6786 ret_val |= MATCH_BIGFIELD;
6788 break;
6790 case AddFirstPers:
6791 if(srchstr(ADD_PERSONAL, string))
6792 ret_val |= MATCH_BIGFIELD;
6794 break;
6796 case AddFirstGlob:
6797 if(srchstr(ADD_GLOBAL, string))
6798 ret_val |= MATCH_BIGFIELD;
6800 break;
6802 case NoAbooks:
6803 if(srchstr(NOABOOKS, string))
6804 ret_val |= MATCH_BIGFIELD;
6806 break;
6808 default:
6809 break;
6811 break;
6813 case 3: /* fcc */
6814 switch(dl->type){
6815 case Simple:
6816 case ListHead:
6817 if(abe && srchstr(abe->fcc, string))
6818 ret_val |= MATCH_FCC;
6820 default:
6821 break;
6823 break;
6825 case 4: /* comment */
6826 switch(dl->type){
6827 case Simple:
6828 case ListHead:
6829 if(abe){
6830 size_t n, len;
6831 unsigned char *p, *tmp = NULL;
6833 if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){
6834 len = n+1;
6835 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
6837 else{
6838 len = SIZEOF_20KBUF;
6839 p = (unsigned char *)tmp_20k_buf;
6842 if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra),
6843 string))
6844 ret_val |= MATCH_COMMENT;
6846 if(tmp)
6847 fs_give((void **)&tmp);
6851 default:
6852 break;
6854 break;
6858 return(ret_val);
6863 * These chars in nicknames will mess up parsing.
6865 * Returns 0 if ok, 1 if not.
6866 * Returns an allocated error message on error.
6869 nickname_check(char *nickname, char **error)
6871 register char *t;
6872 char buf[100];
6874 if((t = strindex(nickname, SPACE)) ||
6875 (t = strindex(nickname, ',')) ||
6876 (t = strindex(nickname, '"')) ||
6877 (t = strindex(nickname, ';')) ||
6878 (t = strindex(nickname, ':')) ||
6879 (t = strindex(nickname, '@')) ||
6880 (t = strindex(nickname, '(')) ||
6881 (t = strindex(nickname, ')')) ||
6882 (t = strindex(nickname, '\\')) ||
6883 (t = strindex(nickname, '[')) ||
6884 (t = strindex(nickname, ']')) ||
6885 (t = strindex(nickname, '<')) ||
6886 (t = strindex(nickname, '>'))){
6887 char s[4];
6888 s[0] = '"';
6889 s[1] = *t;
6890 s[2] = '"';
6891 s[3] = '\0';
6892 if(error){
6894 * TRANSLATORS: this is telling the user that one of the characters
6895 * they have included in a nickname will not work. It will say something like
6896 * Blank spaces not allowed in nicknames or
6897 * Commas not...
6898 * etc.
6900 snprintf(buf, sizeof(buf), _("%s not allowed in nicknames"),
6901 *t == SPACE ?
6902 _("Blank spaces") :
6903 *t == ',' ?
6904 _("Commas") :
6905 *t == '"' ?
6906 _("Quotes") :
6908 buf[sizeof(buf)-1] = '\0';
6909 *error = cpystr(buf);
6912 return 1;
6915 return 0;
6919 char *
6920 abook_select_screen(struct pine *ps)
6922 CONF_S *ctmp = NULL, *first_line = NULL;
6923 OPT_SCREEN_S screen;
6924 int adrbknum;
6925 char *helptitle;
6926 HelpType help;
6927 PerAddrBook *pab;
6928 char *abook = NULL;
6930 helptitle = _("HELP FOR SELECTING AN ADDRESS BOOK");
6931 help = h_role_abook_select;
6933 init_ab_if_needed();
6935 for(adrbknum = 0; adrbknum < as.n_addrbk; adrbknum++){
6936 new_confline(&ctmp);
6937 if(!first_line)
6938 first_line = ctmp;
6940 pab = &as.adrbks[adrbknum];
6942 ctmp->value = cpystr((pab && pab->abnick)
6943 ? pab->abnick
6944 : (pab && pab->filename)
6945 ? pab->filename
6946 : "?");
6948 ctmp->d.b.selected = &abook;
6949 ctmp->d.b.abookname = ctmp->value;
6950 ctmp->keymenu = &abook_select_km;
6951 ctmp->help = help;
6952 ctmp->help_title = helptitle;
6953 ctmp->tool = abook_select_tool;
6954 ctmp->flags = CF_STARTITEM;
6955 ctmp->valoffset = 4;
6958 if(first_line){
6959 memset(&screen, 0, sizeof(screen));
6960 (void) conf_scroll_screen(ps, &screen, first_line,
6961 /* TRANSLATORS: Print something1 using something2.
6962 abooks is something1 */
6963 _("SELECT ADDRESS BOOK"), _("abooks"), 0, NULL);
6965 else
6966 q_status_message(SM_ORDER|SM_DING, 3, 3, _("No address books defined!"));
6968 ps->mangled_screen = 1;
6969 return(abook);
6974 abook_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
6976 int retval;
6978 switch(cmd){
6979 case MC_CHOICE :
6980 *((*cl)->d.b.selected) = cpystr((*cl)->d.b.abookname);
6981 retval = simple_exit_cmd(flags);
6982 break;
6984 case MC_EXIT :
6985 retval = simple_exit_cmd(flags);
6986 break;
6988 default:
6989 retval = -1;
6990 break;
6993 if(retval > 0)
6994 ps->mangled_body = 1;
6996 return(retval);
7001 * This isn't actually just a nickname completion anymore. The user types
7002 * in a prefix of either a nickname, or a full address, or the addr@mailbox
7003 * part of an address and we look in all of the address books for matches
7004 * like that. We return the longest unambiguous match in answer.
7006 * Args prefix -- The part of the "nickname" that has been typed so far
7007 * answer -- The answer is returned here.
7008 * tabtab -- If the answer returned from adrbk_list_of_completions is
7009 * ambiguous and tabtab is set, then offer up a
7010 * selector screen for all the possible answers.
7011 * flags -- ANC_AFTERCOMMA -- This means that the passed in
7012 * prefix may be a list of comma
7013 * separated addresses and we're only
7014 * completing the last one.
7016 * Returns 0 -- no matches at all
7017 * 1 -- more than one nickname (or address) begins with
7018 * the answer being returned
7019 * 2 -- the returned answer is a complete answer, either a
7020 * nickname or a complete address, and there are
7021 * no longer matches for the prefix
7023 * Allocated answer is returned in answer argument.
7024 * Caller needs to free the answer.
7027 abook_nickname_complete(char *prefix, char **answer, int tabtab, unsigned flags)
7029 int ambiguity;
7030 COMPLETE_S *completions, *cp;
7031 char *saved_beginning = NULL;
7032 char *potential_answer = NULL;
7034 wp_exit = wp_nobail = 0;
7036 /* there shouldn't be a case where answer is NULL */
7037 if(answer)
7038 *answer = NULL;
7041 * If we need to handle case where no prefix is passed in,
7042 * figure that out when we need it.
7044 if(!(prefix && prefix[0]))
7045 return(0);
7047 if(flags & ANC_AFTERCOMMA){
7048 char *lastnick;
7051 * Find last comma, save the part before that, operate
7052 * only on the last address.
7054 if((lastnick = strrchr(prefix ? prefix : "", ',')) != NULL){
7055 lastnick++;
7056 while(!(*lastnick & 0x80) && isspace((unsigned char) (*lastnick)))
7057 lastnick++;
7059 saved_beginning = cpystr(prefix);
7060 saved_beginning[lastnick-prefix] = '\0';
7061 prefix = lastnick;
7065 if(!(prefix && prefix[0]))
7066 return(0);
7068 completions = adrbk_list_of_completions(prefix,
7069 ps_global->cur_uid_stream, ps_global->cur_uid,
7070 ALC_INCLUDE_ADDRS | ((strlen(prefix) >= 3) ? ALC_INCLUDE_LDAP : 0));
7072 if(!completions)
7073 ambiguity = 0;
7074 else if(completions && completions->next)
7075 ambiguity = 1;
7076 else
7077 ambiguity = 2;
7079 if(ambiguity == 2){
7080 if(completions->full_address && completions->full_address[0])
7081 potential_answer = cpystr(completions->full_address);
7082 else if(completions->nickname && completions->nickname[0])
7083 potential_answer = cpystr(completions->nickname);
7084 else if(completions->addr && completions->addr[0])
7085 potential_answer = cpystr(completions->addr);
7086 else
7087 potential_answer = cpystr(prefix);
7089 /* answer is ambiguous and caller wants a choose list in that case */
7090 else if(ambiguity == 1 && tabtab){
7091 potential_answer = choose_an_address_with_this_prefix(completions);
7093 if(potential_answer)
7094 ambiguity = 2;
7095 else{
7096 ambiguity = 1;
7097 potential_answer = cpystr(prefix);
7100 else if(ambiguity == 1){
7101 int k;
7102 char cand1_kth_char, cand2_kth_char;
7103 char unambig[1000];
7105 /* find the longest unambiguous prefix */
7106 strncpy(unambig, prefix, sizeof(unambig));
7107 unambig[sizeof(unambig)-1] = '\0';
7108 k = strlen(unambig);
7111 * First verify that they all match through prefix. LDAP sometimes gives
7112 * weird, inexplicable answers that don't seem to match at all.
7114 for(cp = completions; cp; cp = cp->next)
7115 if(!( /* not a match */
7116 /* no NICK bit OR nickname matches prefix */
7117 (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && !struncmp(unambig, cp->nickname, k)))
7118 /* AND no ADDR bit OR addr matches prefix */
7119 && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && !struncmp(unambig, cp->addr, k)))
7120 /* AND neither FULL bit is set OR one of the two fulls matches prefix */
7121 && (!(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))))
7123 break;
7125 /* if cp that means there was not a universal match up through prefix, stop */
7126 if(!cp)
7128 cand1_kth_char = cand2_kth_char = '\0';
7129 if(completions->matches_bitmap & ALC_NICK && completions->nickname && strlen(completions->nickname) >= k)
7130 cand1_kth_char = completions->nickname[k];
7131 else if(completions->matches_bitmap & ALC_ADDR && completions->addr && strlen(completions->addr) >= k)
7132 cand1_kth_char = completions->addr[k];
7133 else{
7134 if(completions->matches_bitmap & ALC_FULL && completions->full_address && strlen(completions->full_address) >= k)
7135 cand1_kth_char = completions->full_address[k];
7137 if(completions->matches_bitmap & ALC_REVFULL && completions->rev_fullname && strlen(completions->rev_fullname) >= k)
7138 cand2_kth_char = completions->rev_fullname[k];
7142 * You'll want a wide screen to read this. There are two possible
7143 * candidate chars for the next position. One or the other of them
7144 * has to match in all of the possible completions. We consider it
7145 * a match if either of the fullname completions for this entry is
7146 * a match. That may not match what the user expects but it may.
7148 for(cp = completions; cp; cp = cp->next){
7149 if(!( /* candidate 1 is not a match */
7150 /* candidate 1 is defined */
7151 cand1_kth_char && cand1_kth_char != ','
7152 /* AND no NICK bit OR nickname char is a match */
7153 && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand1_kth_char))
7154 /* AND no ADDR bit OR addr char is a match */
7155 && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand1_kth_char))
7156 /* AND neither FULL bit is set OR one of the two full chars is a match */
7157 && (!(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)))
7159 cand1_kth_char = '\0'; /* mark that it isn't a match */
7161 if(!cand1_kth_char && !( /* cand1 is not a match AND cand2 is not a match */
7162 /* candidate 2 is defined */
7163 cand2_kth_char && cand2_kth_char != ','
7164 /* AND no NICK bit OR nickname char is a match */
7165 && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand2_kth_char))
7166 /* AND no ADDR bit OR addr char is a match */
7167 && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand2_kth_char))
7168 /* AND neither FULL bit is set OR one of the two full chars is a match */
7169 && (!(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)))
7171 cand2_kth_char = '\0'; /* mark that it isn't a match */
7173 if(!cand1_kth_char && !cand2_kth_char)
7174 break; /* no match so break */
7177 if(!cp) /* they all matched */
7178 unambig[k++] = cand1_kth_char ? cand1_kth_char : cand2_kth_char;
7180 }while(!cp && k < sizeof(unambig)-1);
7182 unambig[k] = '\0';
7183 unambig[sizeof(unambig)-1] = '\0';
7185 /* don't return answer with trailing space */
7186 while(--k >= 0 && isspace((unsigned char) unambig[k]))
7187 unambig[k] = '\0';
7189 potential_answer = cpystr(unambig);
7192 if(completions)
7193 free_complete_s(&completions);
7195 if(answer && ambiguity != 0){
7196 if(potential_answer){
7197 if(saved_beginning){
7198 size_t l1, l2;
7200 l1 = strlen(saved_beginning);
7201 l2 = strlen(potential_answer);
7202 *answer = (char *) fs_get((l1+l2+1) * sizeof(char));
7203 strncpy(*answer, saved_beginning, l1+l2);
7204 strncpy(*answer+l1, potential_answer, l2);
7205 (*answer)[l1+l2] = '\0';
7207 else{
7208 *answer = potential_answer;
7209 potential_answer = NULL;
7212 else{
7213 /* this can't happen */
7214 ambiguity = 0;
7218 if(saved_beginning)
7219 fs_give((void **) &saved_beginning);
7221 if(potential_answer)
7222 fs_give((void **) &potential_answer);
7224 return(ambiguity);
7229 * Returns an allocated nickname choice from user that begins with
7230 * prefix.
7232 char *
7233 choose_an_address_with_this_prefix(COMPLETE_S *completions)
7235 char buf[1000];
7236 char *chosen_address = NULL;
7237 char **lp, **da, **possible_addrs = NULL, **display_addrs = NULL;
7238 COMPLETE_S *cp;
7239 size_t cnt = 0;
7240 int show_nick, show_revfull;
7243 * Count how many and allocate an array for choose_item_from_list().
7245 for(cnt = 0, cp = completions; cp; cp = cp->next)
7246 cnt++;
7249 * Copy completions into an array.
7251 if(cnt > 0){
7252 lp = possible_addrs = (char **) fs_get((cnt+1) * sizeof(*possible_addrs));
7253 memset(possible_addrs, 0, (cnt+1) * sizeof(*possible_addrs));
7254 da = display_addrs = (char **) fs_get((cnt+1) * sizeof(*display_addrs));
7255 memset(display_addrs, 0, (cnt+1) * sizeof(*display_addrs));
7256 for(cp = completions; cp; cp = cp->next){
7257 show_nick = (cp->matches_bitmap & ALC_NICK) && cp->nickname && cp->nickname[0];
7258 show_revfull = 0;
7259 if(!show_nick && !(cp->matches_bitmap & (ALC_NICK | ALC_ADDR | ALC_FULL))
7260 && (cp->matches_bitmap & ALC_REVFULL)
7261 && cp->rev_fullname && cp->rev_fullname[0])
7262 show_revfull = 1;
7264 snprintf(buf, sizeof(buf), "%s%s%s%s%s",
7265 cp->full_address ? cp->full_address : "?",
7266 (show_nick || show_revfull) ? " (" : "",
7267 show_nick ? cp->nickname : "",
7268 show_revfull ? cp->rev_fullname : "",
7269 (show_nick || show_revfull) ? ")" : "");
7270 *da++ = cpystr(buf);
7271 *lp++ = cpystr(cp->full_address ? cp->full_address : "?");
7275 if(possible_addrs){
7276 chosen_address = choose_item_from_list(possible_addrs, display_addrs,
7277 _("SELECT AN ADDRESS"),
7278 _("addresses"),
7279 h_select_address_screen,
7280 _("HELP FOR SELECTING AN ADDRESS"),
7281 NULL);
7282 free_list_array(&possible_addrs);
7285 if(display_addrs)
7286 free_list_array(&display_addrs);
7288 return(chosen_address);
7292 #ifdef _WINDOWS
7294 * addr_scroll_up - adjust the global screen state struct such that pine's
7295 * window on the data is shifted DOWN (i.e., the data's
7296 * scrolled up).
7299 addr_scroll_up(count)
7300 long count;
7302 int next;
7304 if(count < 0)
7305 return(addr_scroll_down(-count));
7306 else if(count){
7307 long i;
7309 i=count;
7310 as.cur_row += as.top_ent;
7311 while(i && as.top_ent + 1 < as.last_ent){
7312 if(line_is_selectable(as.top_ent)){
7313 if(next_selectable_line(as.top_ent,&next)){
7314 as.cur_row = next;
7315 i--;
7316 as.top_ent++;
7318 else i = 0;
7320 else {
7321 i--;
7322 as.top_ent++;
7325 as.cur_row = as.cur_row - as.top_ent; /* must always be positive */
7327 as.old_cur_row = as.cur_row;
7330 return(1);
7335 * addr_scroll_down - adjust the global screen state struct such that pine's
7336 * window on the data is shifted UP (i.e., the data's
7337 * scrolled down).
7340 addr_scroll_down(count)
7341 long count;
7343 if(count < 0)
7344 return(addr_scroll_up(-count));
7345 else if(count){
7346 long i;
7348 for(i = count; i && as.top_ent; i--, as.top_ent--)
7349 as.cur_row++;
7351 while (as.cur_row >= as.l_p_page){
7352 prev_selectable_line(as.cur_row+as.top_ent, &as.cur_row);
7353 as.cur_row = as.cur_row - as.top_ent;
7356 as.old_cur_row = as.cur_row;
7359 return(1);
7364 * addr_scroll_to_pos - scroll the address book data in pine's window such
7365 * tthat the given "line" is at the top of the page.
7368 addr_scroll_to_pos(line)
7369 long line;
7371 return(addr_scroll_up(line - as.top_ent));
7375 /*----------------------------------------------------------------------
7376 MSWin scroll callback. Called during scroll message processing.
7380 Args: cmd - what type of scroll operation.
7381 scroll_pos - parameter for operation.
7382 used as position for SCROLL_TO operation.
7384 Returns: TRUE - did the scroll operation.
7385 FALSE - was not able to do the scroll operation.
7386 ----*/
7388 addr_scroll_callback (cmd, scroll_pos)
7389 int cmd;
7390 long scroll_pos;
7392 int paint = TRUE;
7394 switch (cmd) {
7395 case MSWIN_KEY_SCROLLUPLINE:
7396 paint = addr_scroll_down (scroll_pos);
7397 break;
7399 case MSWIN_KEY_SCROLLDOWNLINE:
7400 paint = addr_scroll_up (scroll_pos);
7401 break;
7403 case MSWIN_KEY_SCROLLUPPAGE:
7404 paint = addr_scroll_down (as.l_p_page);
7405 break;
7407 case MSWIN_KEY_SCROLLDOWNPAGE:
7408 paint = addr_scroll_up (as.l_p_page);
7409 break;
7411 case MSWIN_KEY_SCROLLTO:
7412 paint = addr_scroll_to_pos (scroll_pos);
7413 break;
7416 if(paint)
7417 display_book(0, as.cur_row, -1, 1, (Pos *)NULL);
7419 return(paint);
7423 char *
7424 pcpine_help_addrbook(title)
7425 char *title;
7428 * Title is size 256. Fix this to pass the titlelen.
7430 if(title)
7431 strncpy(title, (as.config)
7432 ? _("Alpine CONFIGURING ADDRESS BOOKS Help")
7433 : _("Alpine ADDRESS_BOOK Help"), 256);
7435 return(pcpine_help(gAbookHelp));
7437 #endif /* _WINDOWS */