* Rewrite support for specific SSL encryption protocols, including
[alpine.git] / alpine / addrbook.c
blob8bf6c8053e414791b6aea9b5c829b939a6f2a0c1
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-2018 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 paint_line(old_line + HEADER_ROWS(ps_global), as.top_ent + old_line,
378 0, &sp);
381 /*--- paint the position with the cursor ---*/
382 paint_line(cur_line + HEADER_ROWS(ps_global), as.top_ent + cur_line,
383 1, &sp);
384 if(start_pos)
385 *start_pos = sp;
388 #ifdef _WINDOWS
389 scroll_setpos(as.top_ent);
390 mswin_endupdate();
391 #endif
392 fflush(stdout);
397 * Paint a line on the screen
399 * Args: line -- Line on screen to paint
400 * global_row -- Row number of line to paint
401 * highlight -- Line should be highlighted
402 * start_pos -- return position where text begins here
404 * Result: Line is painted
406 * The three field widths for the formatting are passed in. There is an
407 * implicit 2 spaces between the fields.
409 * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ...
411 void
412 paint_line(int line, long int global_row, int highlight, Pos *start_pos)
414 int scol, bolden = 0;
415 char *start_hilite_here, *end_hilite_here;
416 AddrScrn_Disp *dl;
417 char lbuf[6*MAX_SCREEN_COLS + 1];
418 register char *p;
420 dprint((10, "- paint_line(%d, %d) -\n", line, highlight));
422 dl = dlist(global_row);
423 start_pos->row = line;
424 start_pos->col = 0; /* default */
426 switch(dl->type){
427 case Beginning:
428 case End:
429 return;
431 default:
432 break;
435 p = get_abook_display_line(global_row, line == HEADER_ROWS(ps_global),
436 highlight ? &start_hilite_here : NULL,
437 highlight ? &end_hilite_here : NULL,
438 &scol, lbuf, sizeof(lbuf));
440 if(as.selections &&
441 as.do_bold &&
442 (dl->type == ListHead || dl->type == Simple)){
443 PerAddrBook *pab;
445 pab = &as.adrbks[adrbk_num_from_lineno(global_row)];
446 if(entry_is_selected(pab->address_book->selects, (a_c_arg_t)dl->elnum)){
447 bolden++;
448 StartBold();
452 if(p && *p){
453 if(highlight){
454 char save_char;
456 MoveCursor(line, 0);
459 * print part before highlight starts
461 if(start_hilite_here != NULL){
462 save_char = *start_hilite_here;
463 *start_hilite_here = '\0';
464 Write_to_screen(p);
465 *start_hilite_here = save_char;
469 * print highlighted part
472 if(end_hilite_here != NULL){
473 save_char = *end_hilite_here;
474 *end_hilite_here = '\0';
477 StartInverse();
478 Write_to_screen(start_hilite_here ? start_hilite_here : p);
479 EndInverse();
482 * print part after highlight ends
484 if(end_hilite_here != NULL){
485 *end_hilite_here = save_char;
486 Write_to_screen(end_hilite_here);
489 else
490 PutLine0(line, 0, p);
493 if(bolden)
494 EndBold();
496 if(scol > 0)
497 start_pos->col = scol;
502 * Assemble a line suitable for displaying on screen
504 * Args: global_row -- Row number of line to assemble.
505 * continuation -- This is the top line of screen display
506 * s_hilite -- Location in the returned line where highlight
507 * should start, if desired
508 * e_hilite -- Location in the returned line where highlight
509 * should end, if desired
510 * retcol -- Return column where text begins here.
511 * lbuf -- Put the output here. Lbuf should be at least
512 * size 6*screen_cols+1.
513 * lbufsize -- Size of lbuf array
515 * Result: Pointer to lbuf is returned.
517 * There is an implicit 2 spaces between the fields.
519 * | fld_width[0] chars |__| fld_width[1] |__| fld_width[2] | ...
521 char *
522 get_abook_display_line(long int global_row, int continuation, char **s_hilite,
523 char **e_hilite, int *retcol, char *lbuf, size_t lbufsize)
525 int fld_col[NFIELDS],
526 fld_width[NFIELDS],
527 screen_width, width,
528 col, special_col = 0,
529 width_consumed,
530 scol = -1,
531 fld;
532 char *string, *writeptr;
533 char special[6*MAX_SCREEN_COLS-1];
534 AddrScrn_Disp *dl;
535 AdrBk_Entry *abe;
536 PerAddrBook *pab;
537 #define LSPACE() (lbufsize - (writeptr - lbuf) - 1)
539 dprint((10, "- get_display_line(%d) -\n", global_row));
541 dl = dlist(global_row);
542 if(retcol)
543 *retcol = 0; /* default */
545 if(s_hilite){
546 *s_hilite = NULL; /* NULL in these means to hilight whole line */
547 *e_hilite = NULL;
550 writeptr = lbuf;
551 memset(writeptr, 0, lbufsize);
552 memset(special, 0, sizeof(special));
554 switch(dl->type){
555 case Beginning:
556 case End:
557 return lbuf;
559 default:
560 break;
563 screen_width = ps_global->ttyo->screen_cols;
565 /* the types in this set span all columns */
566 switch(dl->type){
567 /* center these */
568 case Text:
569 case Title:
570 case TitleCmb:
571 case ClickHereCmb:
572 case Empty:
573 case ZoomEmpty:
574 case NoAbooks:
575 if(dl->type == Empty)
576 string = EMPTY;
577 else if(dl->type == ZoomEmpty)
578 string = ZOOM_EMPTY;
579 else if(dl->type == NoAbooks)
580 string = NOABOOKS;
581 else if(dl->type == ClickHereCmb)
582 string = CLICKHERECMB;
583 else
584 string = dl->usst;
586 /* center it */
587 col = (screen_width - (int) utf8_width(string))/2;
588 col = MAX(col, 0);
589 width_consumed = 0;
591 /* col spaces to start */
592 if(col > 0 && LSPACE() >= col){
593 memset(writeptr, ' ', col);
594 writeptr += col;
595 width_consumed += col;
598 if((width=utf8_width(string)) <= screen_width-col){
599 strncpy(writeptr, string, LSPACE());
600 width_consumed += width;
602 else{
603 utf8_pad_to_width(writeptr, string, LSPACE(), screen_width-col, 1);
604 width_consumed = screen_width;
607 if(s_hilite && *s_hilite == NULL){
608 *s_hilite = writeptr;
609 *e_hilite = writeptr + strlen(writeptr);
612 writeptr += strlen(writeptr);
613 if(width_consumed < screen_width)
614 memset(writeptr, ' ', screen_width-width_consumed);
616 if(retcol)
617 *retcol = col;
619 return lbuf;
621 /* left adjust these */
622 case AddFirstPers:
623 case AddFirstGlob:
624 case AskServer:
625 if(dl->type == AddFirstPers)
626 string = ADD_PERSONAL;
627 else if(dl->type == AddFirstGlob)
628 string = ADD_GLOBAL;
629 else
630 string = dl->usst;
632 /* left adjust it */
633 col = 0;
634 width_consumed = 0;
635 if((width=utf8_width(string)) <= screen_width){
636 strncpy(writeptr, string, LSPACE());
637 width_consumed += width;
639 else{
640 utf8_pad_to_width(writeptr, string, LSPACE(), screen_width, 1);
641 width_consumed = screen_width;
644 if(s_hilite && *s_hilite == NULL){
645 *s_hilite = writeptr;
646 *e_hilite = writeptr + strlen(writeptr);
649 writeptr += strlen(writeptr);
650 if(width_consumed < screen_width)
651 memset(writeptr, ' ', screen_width-width_consumed);
653 if(retcol)
654 *retcol = col;
656 return lbuf;
658 default:
659 break;
662 pab = &as.adrbks[adrbk_num_from_lineno(global_row)];
663 for(fld = 0; fld < NFIELDS; fld++)
664 fld_width[fld] = pab->disp_form[fld].width;
666 fld_col[0] = 0;
667 for(fld = 1; fld < NFIELDS; fld++)
668 fld_col[fld] = MIN(fld_col[fld-1]+fld_width[fld-1]+2, screen_width);
670 width_consumed = 0;
672 /* fill in the fields */
673 for(fld = 0; fld < NFIELDS; fld++){
674 if(fld_width[fld] == 0
675 && !(pab->disp_form[fld].type == Addr
676 && (dl->type == ListClickHere || dl->type == ListEmpty)))
677 continue;
679 switch(pab->disp_form[fld].type){
680 case Notused:
681 break;
683 case Nickname:
684 switch(dl->type){
685 case Simple:
686 case ListHead:
687 abe = ae(global_row);
688 string = (abe && abe->nickname) ? abe->nickname : "";
689 if(scol == -1)
690 scol = fld_col[fld];
692 /* left adjust string in field */
693 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
694 writeptr += strlen(writeptr);
695 width_consumed += fld_width[fld];
696 break;
698 default:
699 break;
702 break;
704 case Fullname:
705 switch(dl->type){
706 case Simple:
707 case ListHead:
708 abe = ae(global_row);
709 string = (abe && abe->fullname) ? abe->fullname : "";
710 if(scol == -1)
711 scol = fld_col[fld];
713 /* left adjust string in field */
714 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
715 writeptr += strlen(writeptr);
716 width_consumed += fld_width[fld];
717 break;
719 case ListEnt:
720 /* continuation line */
721 if(continuation){
722 int width1, width2;
724 width2 = MIN(11, fld_width[fld]);
725 width1 = MAX(fld_width[fld] - width2 - 1, 0);
727 abe = ae(global_row);
728 string = (abe && abe->fullname) ? abe->fullname : "";
730 if(width1){
731 utf8_pad_to_width(writeptr, string, LSPACE(), width1, 1);
732 writeptr += strlen(writeptr);
733 if(LSPACE() > 0)
734 *writeptr++ = ' ';
737 /* TRANSLATORS: continuation line meaning there is more on the next page */
738 if(width2 && LSPACE() >= strlen(_("(continued)"))){
739 strncpy(writeptr, _("(continued)"), width2);
740 lbuf[lbufsize-1] = '\0';
741 writeptr += strlen(writeptr);
744 width_consumed += fld_width[fld];
746 lbuf[lbufsize-1] = '\0';
749 break;
751 default:
752 break;
755 break;
757 case Addr:
758 switch(dl->type){
759 case ListClickHere:
760 case ListEmpty:
761 if(dl->type == ListClickHere)
762 string = CLICKHERE;
763 else
764 string = EMPTY;
766 if((width=utf8_width(string)) <= fld_width[fld]){
768 strncpy(writeptr, string, LSPACE());
770 if(s_hilite && *s_hilite == NULL){
771 *s_hilite = writeptr;
772 *e_hilite = writeptr + strlen(writeptr);
775 writeptr += strlen(writeptr);
776 width_consumed += width;
778 if(scol == -1)
779 scol = fld_col[fld];
781 else{
783 * Place the string in special array and overlay it
784 * onto the right edge of the screen at the end.
786 if(width <= screen_width){
787 strncpy(special, string, sizeof(special));
788 special_col = screen_width - width;
790 else{
791 utf8_pad_to_width(special, string, sizeof(special), screen_width, 1);
792 special_col = 0;
795 special[sizeof(special)-1] = '\0';
798 break;
800 case ListHead:
801 if(scol == -1)
802 scol = fld_col[fld];
804 string = DISTLIST;
805 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
806 writeptr += strlen(writeptr);
807 width_consumed += fld_width[fld];
808 break;
810 case Simple:
811 abe = ae(global_row);
812 #ifdef ENABLE_LDAP
813 if(abe && abe->addr.addr &&
814 !strncmp(abe->addr.addr, QRUN_LDAP, LEN_QRL))
815 string = LDAP_DISP;
816 else
817 #endif
818 string = (abe && abe->tag == Single && abe->addr.addr) ?
819 abe->addr.addr : "";
820 if(scol == -1)
821 scol = fld_col[fld];
823 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
824 writeptr += strlen(writeptr);
825 width_consumed += fld_width[fld];
826 break;
828 case ListEnt:
829 string = listmem(global_row) ? listmem(global_row) : "";
830 if(scol == -1)
831 scol = fld_col[fld];
833 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
835 if(s_hilite && *s_hilite == NULL){
836 *s_hilite = writeptr;
837 *e_hilite = writeptr + strlen(writeptr);
840 writeptr += strlen(writeptr);
841 width_consumed += fld_width[fld];
843 break;
845 default:
846 break;
848 break;
850 case Filecopy:
851 case Comment:
852 switch(dl->type){
853 case Simple:
854 case ListHead:
855 abe = ae(global_row);
856 if(pab->disp_form[fld].type == Filecopy)
857 string = (abe && abe->fcc) ? abe->fcc : "";
858 else
859 string = (abe && abe->extra) ? abe->extra : "";
861 if(scol == -1)
862 scol = fld_col[fld];
864 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
865 writeptr += strlen(writeptr);
866 width_consumed += fld_width[fld];
867 break;
869 default:
870 break;
873 break;
875 case Checkbox:
876 switch(dl->type){
877 case Simple:
878 case ListHead:
879 if(entry_is_checked(pab->address_book->checks,
880 (a_c_arg_t)dl->elnum))
881 string = "[X]";
882 else
883 string = "[ ]";
885 if(scol == -1)
886 scol = fld_col[fld];
888 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
889 writeptr += strlen(writeptr);
890 width_consumed += fld_width[fld];
891 break;
893 default:
894 break;
897 break;
899 case Selected:
900 switch(dl->type){
901 case Simple:
902 case ListHead:
903 if(entry_is_selected(pab->address_book->selects,
904 (a_c_arg_t)dl->elnum))
905 string = "X";
906 else
907 string = " ";
909 if(scol == -1)
910 scol = fld_col[fld];
912 utf8_pad_to_width(writeptr, string, LSPACE(), fld_width[fld], 1);
913 writeptr += strlen(writeptr);
914 width_consumed += fld_width[fld];
915 break;
917 default:
918 break;
921 break;
923 case WhenNoAddrDisplayed:
924 switch(dl->type){
925 case ListClickHere:
926 case ListEmpty:
927 case ListEnt:
928 if(dl->type == ListClickHere)
929 string = CLICKHERE;
930 else if(dl->type == ListEmpty)
931 string = EMPTY;
932 else
933 string = listmem(global_row) ? listmem(global_row) : "";
935 if((width=utf8_width(string)) <= fld_width[fld])
936 strncpy(special, string, sizeof(special));
937 else
938 utf8_pad_to_width(special, string, sizeof(special), fld_width[fld], 1);
940 special[sizeof(special)-1] = '\0';
941 special_col = screen_width - fld_width[fld];
942 special_col = MAX(0, special_col);
943 break;
945 default:
946 break;
949 break;
951 default:
952 break;
955 if(fld < NFIELDS-1)
956 while(width_consumed < fld_col[fld+1] && LSPACE() > 0){
957 *writeptr++ = ' ';
958 width_consumed++;
963 * Right adjust the special string in lbuf, writing over
964 * anything in our way.
966 if(special[0]){
967 unsigned got_width;
969 writeptr = utf8_count_forw_width(lbuf, special_col, &got_width);
971 strncpy(writeptr, special, LSPACE());
973 if(scol == -1)
974 scol = got_width;
976 if(s_hilite && *s_hilite == NULL){
977 *s_hilite = writeptr;
978 *e_hilite = writeptr + strlen(writeptr);
981 writeptr += strlen(writeptr);
983 width_consumed = (int) utf8_width(lbuf);
984 while(width_consumed++ < screen_width && LSPACE() > 0)
985 *writeptr++ = ' ';
987 if(LSPACE() > 0)
988 *writeptr = '\0';
991 if(scol > 0 && retcol)
992 *retcol = scol;
994 lbuf[lbufsize-1] = '\0';
995 return lbuf;
1000 * Set field widths for the columns of the display. The idea is to
1001 * try to come up with something that works pretty well. Getting it just
1002 * right isn't important.
1004 * Col1 and col2 are arrays which contain some widths useful for
1005 * formatting the screen. The 0th element is the max
1006 * width in that column. The 1st element is the max of the third largest
1007 * width in each addressbook (yup, strange).
1009 * The info above applies to the default case (AllAuto). The newer methods
1010 * where the user specifies the format is the else part of the big if and
1011 * is quite a bit different. It's all sort of ad hoc when we're asked
1012 * to calculate one of the fields for the user.
1014 * Returns non-zero if the widths changed since the last time called.
1017 calculate_field_widths(void)
1019 int space_left, i, j, screen_width;
1020 int ret = 0;
1021 PerAddrBook *pab;
1022 WIDTH_INFO_S *widths;
1023 int max_nick, max_full, max_addr, third_full, third_addr, third_fcc;
1024 int col1[5], col2[5];
1025 int nick = -1, full = -1, addr = -1;
1026 #define SEP 2 /* space between columns */
1028 dprint((9, "- calculate_field_widths -\n"));
1030 screen_width = ps_global->ttyo->screen_cols;
1032 /* calculate widths for each addrbook independently */
1033 for(j = 0; j < as.n_addrbk; j++){
1034 pab = &as.adrbks[j];
1036 max_nick = 0;
1037 max_full = 2;
1038 max_addr = 2;
1039 third_full = 2;
1040 third_addr = 2;
1041 third_fcc = 2;
1043 if(pab->address_book){
1044 widths = &pab->address_book->widths;
1045 max_nick = MIN(MAX(max_nick, widths->max_nickname_width), 25);
1046 max_full = MAX(max_full, widths->max_fullname_width);
1047 max_addr = MAX(max_addr, widths->max_addrfield_width);
1048 third_full = MAX(third_full, widths->third_biggest_fullname_width);
1049 if(third_full == 2)
1050 third_full = MAX(third_full, 2*max_full/3);
1052 third_addr = MAX(third_addr, widths->third_biggest_addrfield_width);
1053 if(third_addr == 2)
1054 third_addr = MAX(third_addr, 2*max_addr/3);
1056 third_fcc = MAX(third_fcc, widths->third_biggest_fccfield_width);
1057 if(third_fcc == 2)
1058 third_fcc = MAX(third_fcc, 2*widths->max_fccfield_width/3);
1061 /* figure out which order they're in and reset widths */
1062 for(i = 0; i < NFIELDS; i++){
1063 pab->disp_form[i].width = 0;
1064 switch(pab->disp_form[i].type){
1065 case Nickname:
1066 nick = i;
1067 break;
1069 case Fullname:
1070 full = i;
1071 break;
1073 case Addr:
1074 addr = i;
1075 break;
1077 default:
1078 break;
1082 /* Compute default format */
1083 if(pab->disp_form[1].wtype == AllAuto){
1085 col1[0] = max_full;
1086 col2[0] = max_addr;
1087 col1[1] = third_full;
1088 col2[1] = third_addr;
1089 col1[2] = 3;
1090 col2[2] = 3;
1091 col1[3] = 2;
1092 col2[3] = 2;
1093 col1[4] = 1;
1094 col2[4] = 1;
1096 space_left = screen_width;
1098 if(pab->disp_form[0].type == Selected ||
1099 pab->disp_form[0].type == Checkbox){
1100 pab->disp_form[0].width = MIN(pab->disp_form[0].req_width,space_left);
1101 space_left = MAX(space_left-(pab->disp_form[0].width + SEP), 0);
1105 * All of the nickname field should be visible,
1106 * and make it at least 3.
1108 pab->disp_form[nick].width = MIN(MAX(max_nick, 3), space_left);
1111 * The SEP is for two blank columns between nickname and next field.
1112 * Those blank columns are taken automatically in paint_line().
1114 space_left -= (pab->disp_form[nick].width + SEP);
1116 if(space_left > 0){
1117 for(i = 0; i < 5; i++){
1118 /* try fitting most of each field in if possible */
1119 if(col1[i] + SEP + col2[i] <= space_left){
1120 int extra;
1122 extra = space_left - col1[i] - SEP - col2[i];
1124 * try to stabilize nickname column shifts
1125 * so that screen doesn't jump around when we make changes
1127 if(i == 0 && pab->disp_form[nick].width < 7 &&
1128 extra >= (7 - pab->disp_form[nick].width)){
1129 extra -= (7 - pab->disp_form[nick].width);
1130 space_left -= (7 - pab->disp_form[nick].width);
1131 pab->disp_form[nick].width = 7;
1134 pab->disp_form[addr].width = col2[i] + extra/2;
1135 pab->disp_form[full].width =
1136 space_left - SEP - pab->disp_form[addr].width;
1137 break;
1142 * None of them would fit. Toss addr field.
1144 if(i == 5){
1145 pab->disp_form[full].width = space_left;
1146 pab->disp_form[addr].width = 0;
1149 else{
1150 pab->disp_form[full].width = 0;
1151 pab->disp_form[addr].width = 0;
1154 dprint((10, "Using %s choice: %d %d %d", enth_string(i+1),
1155 pab->disp_form[nick].width, pab->disp_form[full].width,
1156 pab->disp_form[addr].width));
1158 else{ /* non-default case */
1159 int some_to_calculate = 0;
1160 int columns = 0;
1161 int used = 0;
1162 int avail_screen;
1163 int all_percents = 1;
1164 int pc_tot;
1167 * First count how many fields there are.
1168 * Fill in all the Fixed's while we're at it.
1170 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1171 if(pab->disp_form[i].wtype == Fixed){
1172 pab->disp_form[i].width = pab->disp_form[i].req_width;
1173 all_percents = 0;
1175 else if(pab->disp_form[i].wtype == WeCalculate){
1176 pab->disp_form[i].width = pab->disp_form[i].req_width; /* for now */
1177 some_to_calculate++;
1178 all_percents = 0;
1181 if(pab->disp_form[i].wtype != Special){
1182 used += pab->disp_form[i].width;
1183 columns++;
1187 used += ((columns-1) * SEP);
1188 avail_screen = screen_width - used;
1191 * Now that we know how much space we've got, we can
1192 * calculate the Percent columns.
1194 if(avail_screen > 0){
1195 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1196 if(pab->disp_form[i].wtype == Percent){
1197 /* The 2, 200, and +100 are because we're rounding */
1198 pab->disp_form[i].width =
1199 ((2*pab->disp_form[i].req_width*avail_screen)+100) / 200;
1200 used += pab->disp_form[i].width;
1205 space_left = screen_width - used;
1207 if(space_left < 0){
1209 * If they're all percentages, and the percentages add up to 100,
1210 * then we should fix the rounding problem.
1212 pc_tot = 0;
1213 if(all_percents){
1214 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++)
1215 if(pab->disp_form[i].wtype == Percent)
1216 pc_tot += pab->disp_form[i].req_width;
1219 /* fix the rounding problem */
1220 if(all_percents && pc_tot <= 100){
1221 int col = columns;
1222 int this_col = 0;
1223 int fix = used - screen_width;
1225 while(fix--){
1226 if(col < 0)
1227 col = columns;
1229 /* find col'th column */
1230 for(i=0, this_col=0; i < NFIELDS; i++){
1231 if(pab->disp_form[i].wtype == Percent){
1232 if(col == ++this_col)
1233 break;
1237 pab->disp_form[i].width--;
1238 col--;
1242 * Assume they meant to have them add up to over 100%, so we
1243 * just truncate the right hand edge.
1245 else{
1246 int this_fix, space_over;
1248 /* have to reduce space_over down to zero. */
1249 space_over = used - screen_width;
1250 for(i=NFIELDS-1; i >= 0 && space_over > 0; i--){
1251 if(pab->disp_form[i].type != Notused){
1252 this_fix = MIN(pab->disp_form[i].width, space_over);
1253 pab->disp_form[i].width -= this_fix;
1254 space_over -= this_fix;
1259 else if(space_left > 0){
1260 if(some_to_calculate){
1261 /* make nickname big enough to show all nicknames */
1262 if(nick >= 0 && pab->disp_form[nick].wtype == WeCalculate){
1263 --some_to_calculate;
1264 if(pab->disp_form[nick].width != max_nick){
1265 int this_fix;
1267 this_fix = MIN(max_nick-pab->disp_form[nick].width, space_left);
1268 pab->disp_form[nick].width += this_fix;
1269 space_left -= this_fix;
1273 if(!some_to_calculate && space_left > 0)
1274 goto none_to_calculate;
1276 if(space_left > 0){
1277 int weight = 0;
1278 int used_wt = 0;
1280 /* add up total weight */
1281 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1282 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1283 switch(pab->disp_form[i].type){
1284 case Fullname:
1285 weight += MAX(third_full, pab->disp_form[i].width);
1286 used_wt += pab->disp_form[i].width;
1287 break;
1289 case Addr:
1290 weight += MAX(third_addr, pab->disp_form[i].width);
1291 used_wt += pab->disp_form[i].width;
1292 break;
1294 case Filecopy:
1295 weight += MAX(third_fcc, pab->disp_form[i].width);
1296 used_wt += pab->disp_form[i].width;
1297 break;
1299 default:
1300 break;
1305 if(weight > 0){
1306 int this_fix;
1308 if(weight - used_wt <= space_left){
1309 for(i = 0;
1310 i < NFIELDS && pab->disp_form[i].type != Notused;
1311 i++){
1312 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1313 switch(pab->disp_form[i].type){
1314 case Fullname:
1315 this_fix = third_full - pab->disp_form[i].width;
1316 space_left -= this_fix;
1317 pab->disp_form[i].width += this_fix;
1318 break;
1320 case Addr:
1321 this_fix = third_addr - pab->disp_form[i].width;
1322 space_left -= this_fix;
1323 pab->disp_form[i].width += this_fix;
1324 break;
1326 case Filecopy:
1327 this_fix = third_fcc - pab->disp_form[i].width;
1328 space_left -= this_fix;
1329 pab->disp_form[i].width += this_fix;
1330 break;
1332 default:
1333 break;
1338 /* if still space left and a comment field, all to comment */
1339 if(space_left){
1340 for(i = 0;
1341 i < NFIELDS && pab->disp_form[i].type != Notused;
1342 i++){
1343 if(pab->disp_form[i].type == Comment &&
1344 pab->disp_form[i].wtype == WeCalculate){
1345 pab->disp_form[i].width += space_left;
1346 space_left = 0;
1351 else{ /* not enough space, dole out weighted pieces */
1352 int was_sl = space_left;
1354 for(i = 0;
1355 i < NFIELDS && pab->disp_form[i].type != Notused;
1356 i++){
1357 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1358 switch(pab->disp_form[i].type){
1359 case Fullname:
1360 /* round down */
1361 this_fix = (third_full * was_sl)/weight;
1362 space_left -= this_fix;
1363 pab->disp_form[i].width += this_fix;
1364 break;
1366 case Addr:
1367 this_fix = (third_addr * was_sl)/weight;
1368 space_left -= this_fix;
1369 pab->disp_form[i].width += this_fix;
1370 break;
1372 case Filecopy:
1373 this_fix = (third_fcc * was_sl)/weight;
1374 space_left -= this_fix;
1375 pab->disp_form[i].width += this_fix;
1376 break;
1378 default:
1379 break;
1386 /* give out rest */
1387 while(space_left > 0){
1388 for(i=NFIELDS-1; i >= 0 && space_left > 0; i--){
1389 if(i != nick && pab->disp_form[i].wtype == WeCalculate){
1390 pab->disp_form[i].width++;
1391 space_left--;
1397 else{
1399 * If they're all percentages, and the percentages add up to 100,
1400 * then we just have to fix a rounding problem. Otherwise, we'll
1401 * assume the user meant to have them add up to less than 100.
1403 none_to_calculate:
1404 pc_tot = 0;
1405 if(all_percents){
1406 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++)
1407 if(pab->disp_form[i].wtype == Percent)
1408 pc_tot += pab->disp_form[i].req_width;
1411 if(all_percents && pc_tot >= 100){
1412 int col = columns;
1413 int this_col = 0;
1414 int fix = screen_width - used;
1416 while(fix--){
1417 if(col < 0)
1418 col = columns;
1420 /* find col'th column */
1421 for(i=0, this_col=0; i < NFIELDS; i++){
1422 if(pab->disp_form[i].wtype == Percent){
1423 if(col == ++this_col)
1424 break;
1428 pab->disp_form[i].width++;
1429 col--;
1432 /* else, user specified less than 100%, leave it */
1435 /* else space_left == zero, nothing to do */
1438 * Check for special case. If we find it, this is the case where
1439 * we want to display the list entry field even though there is no
1440 * address field displayed. All of the display width is probably
1441 * used up by now, so we just need to pick some arbitrary width
1442 * for these lines. Since these lines are separate from the other
1443 * lines we've been calculating, we don't have to worry about running
1444 * into them, except for list continuation lines which we're not
1445 * going to worry about.
1447 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1448 if(pab->disp_form[i].wtype == Special){
1449 pab->disp_form[i].width = MIN(utf8_width(CLICKHERE), screen_width);
1450 break;
1455 /* check for width changes */
1456 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1457 if(pab->disp_form[i].width != pab->disp_form[i].old_width){
1458 ret++; /* Tell the caller the screen changed */
1459 pab->disp_form[i].old_width = pab->disp_form[i].width;
1463 pab->nick_is_displayed = 0;
1464 pab->full_is_displayed = 0;
1465 pab->addr_is_displayed = 0;
1466 pab->fcc_is_displayed = 0;
1467 pab->comment_is_displayed = 0;
1468 for(i = 0; i < NFIELDS && pab->disp_form[i].type != Notused; i++){
1469 if(pab->disp_form[i].width > 0){
1470 switch(pab->disp_form[i].type){
1471 case Nickname:
1472 pab->nick_is_displayed++;
1473 break;
1474 case Fullname:
1475 pab->full_is_displayed++;
1476 break;
1477 case Addr:
1478 pab->addr_is_displayed++;
1479 break;
1480 case Filecopy:
1481 pab->fcc_is_displayed++;
1482 break;
1483 case Comment:
1484 pab->comment_is_displayed++;
1485 break;
1486 default:
1487 break;
1493 if(ret)
1494 dprint((9, " some widths changed\n"));
1496 return(ret);
1500 void
1501 redraw_addr_screen(void)
1503 dprint((7, "- redraw_addr_screen -\n"));
1505 ab_resize();
1506 if(as.l_p_page <= 0)
1507 return;
1509 (void)calculate_field_widths();
1510 display_book(0, as.cur_row, -1, 1, (Pos *)NULL);
1515 * Little front end for address book screen so it can be called out
1516 * of the main command loop in alpine.c
1518 void
1519 addr_book_screen(struct pine *pine_state)
1521 dprint((3, "\n\n --- ADDR_BOOK_SCREEN ---\n\n"));
1523 mailcap_free(); /* free resources we won't be using for a while */
1525 if(setjmp(addrbook_changed_unexpectedly)){
1526 /* TRANSLATORS: a warning message telling the user that the address book
1527 is being reset (re-sychronized, restarted) */
1528 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1529 dprint((1, "RESETTING address book... addr_book_screen!\n"));
1530 addrbook_reset();
1533 ab_nesting_level = 1; /* come here only from main menu */
1535 /* TRANSLATORS: a screen title for address book screen. Almost all of
1536 the translatable strings which are all upper case are screen titles. */
1537 (void)addr_book(AddrBookScreen, _("ADDRESS BOOK"), NULL);
1538 end_adrbks();
1540 pine_state->prev_screen = addr_book_screen;
1544 void
1545 addr_book_config(struct pine *pine_state, int edit_exceptions)
1547 if(edit_exceptions){
1548 q_status_message(SM_ORDER, 3, 7,
1549 _("Exception Setup not implemented for address books"));
1550 return;
1553 dprint((3, "\n\n --- ADDR_BOOK_CONFIG ---\n\n"));
1555 mailcap_free(); /* free resources we won't be using for a while */
1557 if(setjmp(addrbook_changed_unexpectedly)){
1558 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1559 dprint((1, "RESETTING address book... addr_book_config!\n"));
1560 addrbook_reset();
1563 ab_nesting_level = 1;
1565 /* TRANSLATORS: A screen title */
1566 (void)addr_book(AddrBookConfig, _("SETUP ADDRESS BOOKS"), NULL);
1567 end_adrbks();
1569 pine_state->prev_screen = addr_book_screen;
1574 * Return a single address
1576 * Returns: pointer to returned address, or NULL if nothing returned
1578 char *
1579 addr_book_oneaddr(void)
1581 char *p;
1582 jmp_buf save_jmp_buf;
1583 int *save_nesting_level;
1585 dprint((3, "--- addr_book_oneaddr ---\n"));
1587 save_nesting_level = cpyint(ab_nesting_level);
1588 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1589 if(setjmp(addrbook_changed_unexpectedly)){
1590 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1591 dprint((1, "RESETTING address book... addr_book_oneaddr!\n"));
1592 addrbook_reset();
1593 ab_nesting_level = *save_nesting_level;
1596 ab_nesting_level++;
1598 /* TRANSLATORS: a screen title */
1599 p = addr_book(SelectAddr, _("SELECT ADDRESS"), NULL);
1601 if(ab_nesting_level <= 1)
1602 end_adrbks();
1603 else
1604 ab_nesting_level--;
1606 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1607 if(save_nesting_level)
1608 fs_give((void **)&save_nesting_level);
1610 return(p);
1615 * Return a list of addresses without fullname
1617 * Returns: pointer to returned address, or NULL if nothing returned
1619 char *
1620 addr_book_multaddr_nf(void)
1622 char *p;
1623 jmp_buf save_jmp_buf;
1624 int *save_nesting_level;
1626 dprint((3, "--- addr_book_multaddr_nf ---\n"));
1628 save_nesting_level = cpyint(ab_nesting_level);
1629 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1630 if(setjmp(addrbook_changed_unexpectedly)){
1631 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1632 dprint((1, "RESETTING address book... addr_book_multaddr_nf!\n"));
1633 addrbook_reset();
1634 ab_nesting_level = *save_nesting_level;
1637 ab_nesting_level++;
1639 p = addr_book(SelectMultNoFull, _("SELECT ADDRESS"), NULL);
1641 if(ab_nesting_level <= 1)
1642 end_adrbks();
1643 else
1644 ab_nesting_level--;
1646 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1647 if(save_nesting_level)
1648 fs_give((void **)&save_nesting_level);
1650 return(p);
1655 * Return a single address without fullname phrase
1657 * Returns: pointer to returned address, or NULL if nothing returned
1659 char *
1660 addr_book_oneaddr_nf(void)
1662 char *p;
1663 jmp_buf save_jmp_buf;
1664 int *save_nesting_level;
1666 dprint((3, "--- addr_book_oneaddr_nf ---\n"));
1668 save_nesting_level = cpyint(ab_nesting_level);
1669 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1670 if(setjmp(addrbook_changed_unexpectedly)){
1671 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1672 dprint((1, "RESETTING address book... addr_book_oneaddr_nf!\n"));
1673 addrbook_reset();
1674 ab_nesting_level = *save_nesting_level;
1677 ab_nesting_level++;
1679 p = addr_book(SelectAddrNoFull, _("SELECT ADDRESS"), NULL);
1681 if(ab_nesting_level <= 1)
1682 end_adrbks();
1683 else
1684 ab_nesting_level--;
1686 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1687 if(save_nesting_level)
1688 fs_give((void **)&save_nesting_level);
1690 return(p);
1695 * Call address book from message composer
1697 * Args: error_mess -- pointer to return error messages in (unused here)
1699 * Returns: pointer to returned address, or NULL if nothing returned
1701 char *
1702 addr_book_compose(char **error)
1704 char *p;
1705 jmp_buf save_jmp_buf;
1706 int *save_nesting_level;
1708 dprint((3, "--- addr_book_compose ---\n"));
1710 save_nesting_level = cpyint(ab_nesting_level);
1711 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1712 if(setjmp(addrbook_changed_unexpectedly)){
1713 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1714 dprint((1, "RESETTING address book... addr_book_compose!\n"));
1715 addrbook_reset();
1716 ab_nesting_level = *save_nesting_level;
1719 ab_nesting_level++;
1721 /* TRANSLATORS: a screen title */
1722 p = addr_book(SelectNicksCom, _("COMPOSER: SELECT ADDRESS"), error);
1724 if(ab_nesting_level <= 1)
1725 end_adrbks();
1726 else
1727 ab_nesting_level--;
1729 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1730 if(save_nesting_level)
1731 fs_give((void **)&save_nesting_level);
1733 return(p);
1738 * Call address book from message composer for Lcc line
1740 * Args: error_mess -- pointer to return error messages in (unused here)
1742 * Returns: pointer to returned address, or NULL if nothing returned
1744 char *
1745 addr_book_compose_lcc(char **error)
1747 char *p;
1748 jmp_buf save_jmp_buf;
1749 int *save_nesting_level;
1751 dprint((3, "--- addr_book_compose_lcc ---\n"));
1753 save_nesting_level = cpyint(ab_nesting_level);
1754 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1755 if(setjmp(addrbook_changed_unexpectedly)){
1756 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1757 dprint((1, "RESETTING address book... addr_book_compose_lcc!\n"));
1758 addrbook_reset();
1759 ab_nesting_level = *save_nesting_level;
1762 ab_nesting_level++;
1765 * We used to use SelectAddrLccCom here but decided it wasn't necessary
1766 * to restrict the selection to a list.
1768 /* TRANSLATORS: a screen title, user is composing a message and should select
1769 a distribution list from a list of addresses in the address book. */
1770 p = addr_book(SelectNicksCom, _("COMPOSER: SELECT LIST"), error);
1772 if(ab_nesting_level <= 1)
1773 end_adrbks();
1774 else
1775 ab_nesting_level--;
1777 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1778 if(save_nesting_level)
1779 fs_give((void **)&save_nesting_level);
1781 return(p);
1786 * Call address book from message composer for Lcc line
1788 * Args: error_mess -- pointer to return error messages in (unused here)
1790 * Returns: pointer to returned address, or NULL if nothing returned
1792 char *
1793 addr_book_change_list(char **error)
1795 char *p;
1796 jmp_buf save_jmp_buf;
1797 int *save_nesting_level;
1799 dprint((3, "--- addr_book_change_list ---\n"));
1801 save_nesting_level = cpyint(ab_nesting_level);
1802 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1803 if(setjmp(addrbook_changed_unexpectedly)){
1804 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1805 dprint((1, "RESETTING address book... addr_book_change_list!\n"));
1806 addrbook_reset();
1807 ab_nesting_level = *save_nesting_level;
1810 ab_nesting_level++;
1812 /* TRANSLATORS: a screen title */
1813 p = addr_book(SelectNicksCom, _("ADDRESS BOOK (Update): SELECT ADDRESSES"),
1814 error);
1816 if(ab_nesting_level <= 1)
1817 end_adrbks();
1818 else
1819 ab_nesting_level--;
1821 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1822 if(save_nesting_level)
1823 fs_give((void **)&save_nesting_level);
1825 return(p);
1830 * Call address book from pine_simple_send
1832 * Returns: pointer to returned address, or NULL if nothing returned
1834 char *
1835 addr_book_bounce(void)
1837 char *p;
1838 jmp_buf save_jmp_buf;
1839 int *save_nesting_level;
1841 dprint((3, "- addr_book_bounce -\n"));
1843 save_nesting_level = cpyint(ab_nesting_level);
1844 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1845 if(setjmp(addrbook_changed_unexpectedly)){
1846 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1847 dprint((1, "RESETTING address book...addr_book_bounce!\n"));
1848 addrbook_reset();
1849 ab_nesting_level = *save_nesting_level;
1852 ab_nesting_level++;
1854 p = addr_book(SelectManyNicks, _("SELECT ADDRESSES"), NULL);
1856 if(ab_nesting_level <= 1)
1857 end_adrbks();
1858 else
1859 ab_nesting_level--;
1861 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1862 if(save_nesting_level)
1863 fs_give((void **)&save_nesting_level);
1865 return(p);
1870 * Call address book from take address screen
1872 * Returns: pointer to returned nickname, or NULL if nothing returned
1874 char *
1875 addr_book_takeaddr(void)
1877 char *p;
1878 jmp_buf save_jmp_buf;
1879 int *save_nesting_level;
1881 dprint((3, "- addr_book_takeaddr -\n"));
1883 save_nesting_level = cpyint(ab_nesting_level);
1884 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1885 if(setjmp(addrbook_changed_unexpectedly)){
1886 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1887 dprint((1, "RESETTING address book...addr_book_takeaddr!\n"));
1888 addrbook_reset();
1889 ab_nesting_level = *save_nesting_level;
1892 ab_nesting_level++;
1894 /* TRANSLATORS: a screen title */
1895 p = addr_book(SelectNickTake, _("TAKEADDR: SELECT NICKNAME"), NULL);
1897 if(ab_nesting_level <= 1)
1898 end_adrbks();
1899 else
1900 ab_nesting_level--;
1902 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1903 if(save_nesting_level)
1904 fs_give((void **)&save_nesting_level);
1906 return(p);
1911 * Call address book from editing screen for nickname field.
1913 * Returns: pointer to returned nickname, or NULL if nothing returned
1915 char *
1916 addr_book_nick_for_edit(char **error)
1918 char *p;
1919 jmp_buf save_jmp_buf;
1920 int *save_nesting_level;
1921 int save_n_serv;
1923 dprint((3, "- addr_book_nick_for_edit -\n"));
1925 save_n_serv = as.n_serv;
1927 save_nesting_level = cpyint(ab_nesting_level);
1928 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1929 if(setjmp(addrbook_changed_unexpectedly)){
1930 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1931 dprint((1, "RESETTING address book...addr_book_nick_for_edit!\n"));
1932 addrbook_reset();
1933 ab_nesting_level = *save_nesting_level;
1936 ab_nesting_level++;
1939 * This is kind of hoaky. We want to prevent the Directory Query
1940 * options from turning on when we're coming in looking for a nickname
1941 * and this seemed to be the easiest way to accomplish that.
1943 as.n_serv = 0;
1944 /* TRANSLATORS: a screen title, user selecting a nickname from the address book */
1945 p = addr_book(SelectNickCom, _("SELECT NICKNAME"), error);
1946 as.n_serv = save_n_serv;
1948 if(ab_nesting_level <= 1)
1949 end_adrbks();
1950 else
1951 ab_nesting_level--;
1953 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1954 if(save_nesting_level)
1955 fs_give((void **)&save_nesting_level);
1957 return(p);
1962 * Call address book for generic nickname select
1964 * Returns: pointer to returned nickname, or NULL if nothing returned
1966 char *
1967 addr_book_selnick(void)
1969 char *p;
1970 jmp_buf save_jmp_buf;
1971 int *save_nesting_level;
1973 dprint((3, "- addr_book_selnick -\n"));
1975 save_nesting_level = cpyint(ab_nesting_level);
1976 memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf));
1977 if(setjmp(addrbook_changed_unexpectedly)){
1978 q_status_message(SM_ORDER, 5, 10, _("Resetting address book..."));
1979 dprint((1, "RESETTING address book...addr_book_selnick!\n"));
1980 addrbook_reset();
1981 ab_nesting_level = *save_nesting_level;
1984 ab_nesting_level++;
1986 p = addr_book(SelectNick, _("SELECT NICKNAME"), NULL);
1988 if(ab_nesting_level <= 1)
1989 end_adrbks();
1990 else
1991 ab_nesting_level--;
1993 memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf));
1994 if(save_nesting_level)
1995 fs_give((void **)&save_nesting_level);
1997 return(p);
2002 * Main address book screen
2004 * Control loop for address book. Commands are executed out of a big
2005 * switch and screen painting is done.
2006 * The style argument controls whether or not it is called to return an address
2007 * to the composer, a nickname to the TakeAddr screen, or just for address
2008 * book maintenance.
2010 * Args: style -- how we were called
2012 * Return: might return a string for the composer to edit, or a nickname, ...
2014 char *
2015 addr_book(AddrBookArg style, char *title, char **error_message)
2017 UCS c;
2018 int cmd, r, i,
2019 command_line,
2020 did_delete,
2021 quit, /* loop control */
2022 km_popped, /* menu is popped up in blank menu mode */
2023 current_changed_flag, /* only current row needs update */
2024 was_clickable, is_clickable,
2025 was_collapsible_listent, is_collapsible_listent,
2026 was_global_config, is_global_config,
2027 was_addkey, is_addkey,
2028 was_opened, is_opened,
2029 was_custom_title, is_custom_title,
2030 setall_changed,
2031 start_disp, /* Paint from this line down (0 is top) */
2032 rdonly, /* cur addrbook read only */
2033 empty, /* cur addrbook empty */
2034 are_selecting, /* called as ^T selector */
2035 #ifdef ENABLE_LDAP
2036 directory_ok, /* called from composer, not Lcc */
2037 #endif
2038 from_composer, /* from composer */
2039 listmode_ok, /* ok to do ListMode with this style */
2040 selecting_one_nick,
2041 selecting_mult_nicks,
2042 checkedn, /* how many are checked */
2043 def_key, /* default key */
2044 warped; /* we warped through hyperspace to a
2045 new location in the display list */
2046 long fl,
2047 new_top_ent, /* entry on top of screen after oper */
2048 new_line; /* new line number after operation */
2049 char *addr;
2050 char *utf8str;
2051 bitmap_t bitmap;
2052 struct key_menu *km;
2053 OtherMenu what;
2054 PerAddrBook *pab = NULL;
2055 AddrScrn_Disp *dl;
2056 struct pine *ps;
2057 Pos cursor_pos;
2059 dprint((2, "--- addr_book --- (%s)\n",
2060 style==AddrBookScreen ? "AddrBookScreen" :
2061 style==SelectAddrLccCom ? "SelectAddrLccCom" :
2062 style==SelectNicksCom ? "SelectNicksCom" :
2063 style==SelectNick ? "SelectNick" :
2064 style==SelectNickTake ? "SelectNickTake" :
2065 style==SelectNickCom ? "SelectNickCom" :
2066 style==AddrBookConfig ? "AddrBookConfig" :
2067 style==SelectManyNicks ? "SelectManyNicks" :
2068 style==SelectAddr ? "SelectAddr" :
2069 style==SelectAddrNoFull ? "SelectAddrNoFull" :
2070 style==SelectMultNoFull ? "SelectMultNoFull":
2071 "UnknownStyle"));
2073 km = &ab_keymenu;
2074 ps = ps_global;
2075 ps->next_screen = SCREEN_FUN_NULL;
2077 from_composer = (style == SelectAddrLccCom
2078 || style == SelectNicksCom
2079 || style == SelectNickCom);
2080 are_selecting = (style != AddrBookScreen
2081 && style != AddrBookConfig);
2082 selecting_one_nick = (style == SelectNick
2083 || style == SelectNickTake
2084 || style == SelectNickCom);
2085 selecting_mult_nicks = (style == SelectAddrLccCom
2086 || style == SelectNicksCom
2087 || style == SelectManyNicks);
2088 listmode_ok = (style == SelectAddrLccCom
2089 || style == SelectNicksCom
2090 || style == SelectManyNicks
2091 || style == SelectMultNoFull);
2092 as.config = (style == AddrBookConfig);
2093 #ifdef ENABLE_LDAP
2094 directory_ok = (style == SelectNicksCom
2095 || style == AddrBookScreen
2096 || style == SelectManyNicks);
2097 #endif
2099 /* Coming in from the composer, may need to reset the window */
2100 if(from_composer){
2101 fix_windsize(ps);
2102 init_sigwinch();
2103 mark_status_dirty();
2104 mark_titlebar_dirty();
2105 mark_keymenu_dirty();
2108 command_line = -FOOTER_ROWS(ps); /* third line from the bottom */
2109 what = FirstMenu;
2110 c = 'x'; /* For display_message the first time through */
2112 if(ps->remote_abook_validity > 0)
2113 (void)adrbk_check_and_fix_all(ab_nesting_level == 1, 0, 0);
2115 if(!init_addrbooks(HalfOpen, 1, !as.config, !are_selecting)){
2116 if(are_selecting){
2117 q_status_message(SM_ORDER | SM_DING, 0, 4,
2118 _("No Address Book Configured"));
2119 display_message(c);
2120 sleep(2);
2121 return NULL;
2123 else if(!as.config){
2124 ps->next_screen = main_menu_screen;
2125 q_status_message(SM_ORDER | SM_DING, 3, 4,
2126 _("No Address Book Configured, Use SETUP Addressbook screen"));
2127 ps->mangled_screen = 1;
2128 return NULL;
2131 else if(style == AddrBookScreen && as.n_addrbk == 1 && as.n_serv == 0){
2132 if(as.adrbks[0].access == ReadOnly)
2133 q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
2134 else if(as.adrbks[0].access == NoAccess)
2135 q_status_message(SM_ORDER, 0, 4,
2136 _("AddressBook not accessible, permission denied"));
2139 if(as.l_p_page < 1){
2140 q_status_message(SM_ORDER, 3, 3,
2141 _("Screen too small to use Address book"));
2142 return NULL;
2145 erase_checks();
2146 as.selections = 0;
2147 as.zoomed = 0;
2149 (void) calculate_field_widths();
2151 quit = 0;
2152 km_popped = 0;
2153 ps->mangled_screen = 1;
2154 current_changed_flag = 0;
2155 start_disp = 0;
2156 was_clickable = 0;
2157 is_clickable = 0;
2158 was_collapsible_listent = 0;
2159 is_collapsible_listent = 0;
2160 was_global_config = 0;
2161 is_global_config = 0;
2162 was_addkey = 0;
2163 is_addkey = 0;
2164 was_opened = 0;
2165 is_opened = 0;
2166 was_custom_title = 0;
2167 is_custom_title = 0;
2168 setall_changed = 0;
2169 checkedn = 0;
2172 while(!quit){
2173 ps->user_says_cancel = 0;
2174 if(km_popped){
2175 km_popped--;
2176 if(km_popped == 0){
2177 clearfooter(ps);
2179 * Have to repaint from earliest change down, including
2180 * at least the last two body lines.
2182 if(ps->mangled_body) /* it was already mangled */
2183 start_disp = MIN(start_disp, as.l_p_page -2);
2184 else if(current_changed_flag){
2185 ps->mangled_body = 1;
2186 start_disp = MIN(MIN(as.cur_row, as.l_p_page -2),
2187 as.old_cur_row);
2189 else{
2190 ps->mangled_body = 1;
2191 start_disp = as.l_p_page -2;
2196 ps->redrawer = redraw_addr_screen;
2198 if(new_mail(0, NM_TIMING(c), NM_STATUS_MSG | NM_DEFER_SORT) >= 0)
2199 ps->mangled_header = 1;
2201 if(streams_died())
2202 ps->mangled_header = 1;
2204 if(ps->mangled_screen){
2205 ps->mangled_header = 1;
2206 ps->mangled_body = 1;
2207 ps->mangled_footer = 1;
2208 start_disp = 0;
2209 current_changed_flag = 0;
2210 ps->mangled_screen = 0;
2213 if(ps->mangled_body){
2215 if(calculate_field_widths())
2216 start_disp = 0;
2218 display_book(start_disp,
2219 as.cur_row,
2220 as.old_cur_row,
2222 &cursor_pos);
2224 as.old_cur_row = as.cur_row;
2225 ps->mangled_body = 0;
2226 start_disp = 0;
2227 as.cur = cur_addr_book();
2228 pab = (as.n_addrbk &&
2229 as.cur >= 0 && as.cur < as.n_addrbk &&
2230 !entry_is_askserver(as.top_ent+as.cur_row))
2231 ? &as.adrbks[as.cur] : NULL;
2233 #ifdef _WINDOWS
2235 long i, last_sel;
2237 for(i = as.top_ent; dlist(i)->type != End; i++)
2238 next_selectable_line(i,&last_sel);
2239 as.last_ent = i;
2241 scroll_setrange(as.l_p_page, last_sel);
2243 #endif
2245 /* current entry has been changed */
2246 else if(current_changed_flag){
2247 int need_redraw;
2249 need_redraw = calculate_field_widths();
2251 /*---------- Update the current entry, (move or change) -------*/
2252 display_book(need_redraw ? 0 : as.cur_row,
2253 as.cur_row,
2254 as.old_cur_row,
2255 need_redraw,
2256 &cursor_pos);
2258 as.old_cur_row = as.cur_row;
2259 current_changed_flag = 0;
2260 as.cur = cur_addr_book();
2261 pab = (as.n_addrbk &&
2262 as.cur >= 0 && as.cur < as.n_addrbk &&
2263 !entry_is_askserver(as.top_ent+as.cur_row))
2264 ? &as.adrbks[as.cur] : NULL;
2267 is_custom_title = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) &&
2268 style == AddrBookScreen &&
2269 as.n_addrbk > 1 &&
2270 cur_is_open() &&
2271 pab->abnick &&
2272 pab->abnick[0]);
2273 if(( was_custom_title && !is_custom_title) ||
2274 (!was_custom_title && is_custom_title)){
2275 ps->mangled_header = 1;
2276 was_custom_title = is_custom_title;
2280 if(ps->mangled_header){
2281 char buf[80], *bp;
2283 if(style == AddrBookScreen){
2284 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
2285 if(as.n_addrbk > 1)
2286 snprintf(buf, sizeof(buf), _("ADDRESS BOOKS"));
2287 else
2288 snprintf(buf, sizeof(buf), _("ADDRESS BOOK"));
2290 else
2291 /* TRANSLATORS: a screen title, the %s arguments are for a custom part
2292 of the title. Move them as a group. There are two normal cases.
2293 Either ADDRESS BOOK LIST when a list of names of address books is
2294 displayed, or ADDRESS BOOK <name of address book> when one is open. */
2295 snprintf(buf, sizeof(buf), _("ADDRESS BOOK%s%s%s"),
2296 /* TRANSLATORS: This is the LIST that goes with title above */
2297 is_custom_title ? " <" : cur_is_open() ? "" : _(" LIST"),
2298 is_custom_title ? pab->abnick : "",
2299 is_custom_title ? ">" : "");
2301 buf[sizeof(buf)-1] = '\0';
2303 bp = buf;
2305 else
2306 bp = title;
2308 set_titlebar(bp, ps->mail_stream,
2309 ps->context_current, ps->cur_folder,
2310 ps->msgmap, 1,
2311 FolderName, 0, 0, NULL);
2312 ps->mangled_header = 0;
2315 dprint((9, "addr_book: top of loop, addrbk %d top_ent %ld cur_row %d\n", as.cur, as.top_ent, as.cur_row));
2318 * This is a check to catch when a move from one row to another
2319 * should cause the list of commands to change.
2321 is_clickable = entry_is_clickable(as.top_ent+as.cur_row);
2322 is_collapsible_listent = F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2323 entry_is_listent(as.top_ent+as.cur_row);
2324 is_global_config = as.config && pab && pab->type & GLOBAL;
2325 is_addkey = entry_is_addkey(as.top_ent+as.cur_row);
2326 is_opened = (F_OFF(F_CMBND_ABOOK_DISP,ps_global) &&
2327 cur_is_open());
2328 if(( was_clickable && !is_clickable) ||
2329 (!was_clickable && is_clickable) ||
2330 ( was_collapsible_listent && !is_collapsible_listent) ||
2331 (!was_collapsible_listent && is_collapsible_listent) ||
2332 ( was_global_config && !is_global_config) ||
2333 (!was_global_config && is_global_config) ||
2334 ( was_addkey && !is_addkey) ||
2335 (!was_addkey && is_addkey) ||
2336 ( was_opened && !is_opened) ||
2337 (!was_opened && is_opened) ||
2338 ( setall_changed)){
2340 ps->mangled_footer = 1;
2341 was_clickable = is_clickable;
2342 was_collapsible_listent = is_collapsible_listent;
2343 was_global_config = is_global_config;
2344 was_addkey = is_addkey;
2345 was_opened = is_opened;
2346 setall_changed = 0;
2350 if(ps->mangled_footer){
2352 setbitmap(bitmap);
2353 menu_clear_binding(km, '>');
2354 menu_clear_binding(km, '.');
2355 menu_clear_binding(km, KEY_RIGHT);
2356 menu_clear_binding(km, ctrl('M'));
2357 menu_clear_binding(km, ctrl('J'));
2358 menu_clear_binding(km, KEY_LEFT);
2359 menu_clear_binding(km, '<');
2360 menu_clear_binding(km, ',');
2361 def_key = THREE_KEY; /* default default key */
2362 if(as.config){
2363 km->how_many = 1;
2365 clrbitn(OTHER_KEY, bitmap);
2366 /* TRANSLATORS: a command name for a particular key */
2367 menu_init_binding(km, 'E', MC_EXIT, "E", N_("Exit Setup"), TWO_KEY);
2368 KS_OSDATASET(&km->keys[TWO_KEY], KS_EXITMODE);
2371 * Don't show Delete or Shuffle command if there is nothing
2372 * to delete or shuffle.
2374 if(is_addkey){
2375 clrbitn(DELETE_KEY, bitmap);
2376 clrbitn(SENDTO_KEY, bitmap);
2377 clrbitn(THREE_KEY, bitmap);
2378 menu_init_binding(km, 'A', MC_ADDABOOK, "A",
2379 add_is_global(as.top_ent+as.cur_row)
2380 /* TRANSLATORS: Add Global Address book (one defined by someone else) */
2381 ? "[" N_("Add Glob Abook") "]"
2382 /* TRANSLATORS: Add Personal Address book */
2383 : "[" N_("Add Pers Abook") "]",
2384 ADD_KEY);
2385 def_key = ADD_KEY;
2387 else{
2388 /* TRANSLATORS: Delete Address book command */
2389 menu_init_binding(km, 'D', MC_DELABOOK, "D", N_("Del Abook"),
2390 DELETE_KEY);
2391 /* TRANSLATORS: Shuffle refers to shuffling the order of things,
2392 that is, changing the order */
2393 menu_init_binding(km, '$', MC_SHUFFLE, "$", N_("Shuffle"),
2394 SENDTO_KEY);
2395 /* TRANSLATORS: Change is a command meaning Change some item,
2396 or Edit some item to change it */
2397 menu_init_binding(km, 'C', MC_EDITABOOK, "C", "[" N_("Change") "]",
2398 THREE_KEY);
2399 menu_init_binding(km, 'A', MC_ADDABOOK, "A",
2400 add_is_global(as.top_ent+as.cur_row)
2401 ? N_("Add Glob Abook")
2402 : N_("Add Pers Abook"),
2403 ADD_KEY);
2406 else if(are_selecting){
2407 km->how_many = 1;
2410 * The OTHER_KEY is used as the Exit key in selection mode.
2411 * This is because the TWO_KEY is being used for < actions.
2413 /* TRANSLATORS: Command to Exit the Select screen. This would
2414 be a way to make no Selection and go back to where you
2415 came from. */
2416 menu_init_binding(km, 'E', MC_EXIT, "E", N_("ExitSelect"),
2417 OTHER_KEY);
2418 KS_OSDATASET(&km->keys[OTHER_KEY], KS_EXITMODE);
2421 * Use the TWO_KEY for the go back key.
2423 if(cur_is_open() && (as.n_addrbk > 1 || as.n_serv)){
2424 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2425 entry_is_listent(as.top_ent+as.cur_row))
2426 cmd = MC_UNEXPAND;
2427 else if(F_OFF(F_CMBND_ABOOK_DISP,ps_global))
2428 cmd = MC_POPUP;
2429 else
2430 cmd = MC_NONE;
2432 if(cmd == MC_NONE)
2433 clrbitn(TWO_KEY, bitmap);
2434 else{
2435 menu_init_binding(km, '<', cmd, "<",
2436 cmd == MC_POPUP ? N_("AddressBkList")
2437 /* TRANSLATORS: Unexpand is the opposite of Expand.
2438 We might expand an address book distribution
2439 list to see all the members of the list. */
2440 : N_("Unexpand"),
2441 TWO_KEY);
2442 menu_add_binding(km, ',', cmd);
2443 if(F_ON(F_ARROW_NAV,ps))
2444 menu_add_binding(km, KEY_LEFT, cmd);
2447 else if(as.checkboxes && (as.n_addrbk > 1 || as.n_serv)){
2448 if(checkedn){
2449 if(entry_is_clickable_title(as.top_ent+as.cur_row)){
2450 menu_init_binding(km, 'S', MC_CHOICE, "S",
2451 /* TRANSLATORS: Select something, choose something */
2452 N_("Select"), TWO_KEY);
2454 else{
2455 menu_init_binding(km, 'S', MC_CHOICE, "S",
2456 "[" N_("Select") "]", TWO_KEY);
2457 def_key = TWO_KEY;
2460 else
2461 menu_init_binding(km, 'S', MC_CHOICE, "S", N_("Select"),
2462 TWO_KEY);
2464 else
2465 clrbitn(TWO_KEY, bitmap);
2468 * The THREE_KEY is used as the select key in selection mode,
2469 * but it doesn't show up at the top-level. Instead, the
2470 * key becomes the ViewAbook key.
2472 if(entry_is_askserver(as.top_ent+as.cur_row) && !as.checkboxes){
2473 menu_init_binding(km, '>', MC_QUERY_SERV, ">", "[" N_("Search") "]",
2474 THREE_KEY);
2475 menu_add_binding(km, 's', MC_QUERY_SERV);
2476 menu_add_binding(km, '.', MC_QUERY_SERV);
2477 if(F_ON(F_ARROW_NAV,ps))
2478 menu_add_binding(km, KEY_RIGHT, MC_QUERY_SERV);
2480 else if(entry_is_clickable_title(as.top_ent+as.cur_row)){
2481 /* TRANSLATORS: View this address book */
2482 menu_init_binding(km, '>', MC_OPENABOOK, ">", "[" N_("ViewAbook") "]",
2483 THREE_KEY);
2484 menu_add_binding(km, 'v', MC_OPENABOOK);
2485 menu_add_binding(km, '.', MC_OPENABOOK);
2486 if(F_ON(F_ARROW_NAV,ps))
2487 menu_add_binding(km, KEY_RIGHT, MC_OPENABOOK);
2489 else if(cur_is_open()){
2490 menu_init_binding(km, 'S', MC_CHOICE, "S", "[" N_("Select") "]",
2491 THREE_KEY);
2493 else
2494 clrbitn(THREE_KEY, bitmap);
2496 KS_OSDATASET(&km->keys[THREE_KEY], KS_NONE);
2499 * The Expand command gets stuck out in right field.
2501 if(entry_is_clickable(as.top_ent+as.cur_row) &&
2502 !entry_is_clickable_title(as.top_ent+as.cur_row)){
2503 menu_init_binding(km, '>', MC_EXPAND, ">", N_("Expand"),
2504 SENDTO_KEY);
2505 menu_add_binding(km, '.', MC_EXPAND);
2506 if(F_ON(F_ARROW_NAV,ps))
2507 menu_add_binding(km, KEY_RIGHT, MC_EXPAND);
2509 else
2510 clrbitn(SENDTO_KEY, bitmap);
2512 if(cur_is_open() && as.checkboxes){
2513 /* TRANSLATORS: Set/Unset means that this particular command
2514 will toggle between setting something (turning it on) and
2515 unsetting it (turning it off). For example, it might be
2516 a program option that can be turned on or off or it might
2517 be a way to mark which addresses to send a message to. */
2518 menu_init_binding(km, 'X', MC_TOGGLE, "X", N_("Set/Unset"),
2519 DELETE_KEY);
2522 else if(cur_is_open() && listmode_ok){
2523 /* TRANSLATORS: List mode is a type of screen in pine that
2524 allows the user to select several of something. This is
2525 the name of the command to go into the List mode style
2526 of operating. */
2527 menu_init_binding(km, 'L', MC_LISTMODE, "L", N_("ListMode"),
2528 DELETE_KEY);
2530 else
2531 clrbitn(DELETE_KEY, bitmap);
2533 if(cur_is_open() && as.checkboxes){
2534 menu_init_binding(km, 'A', MC_SELALL, "A",
2535 /* TRANSLATORS: when selecting from a list of items
2536 the unsetall (unset all) means to start over
2537 with nothing selected.
2538 The set all command means select everything
2539 in the list. */
2540 checkedn ? N_("unsetAll") : N_("setAll"),
2541 ADD_KEY);
2543 else
2544 clrbitn(ADD_KEY, bitmap);
2546 KS_OSDATASET(&km->keys[DELETE_KEY], KS_NONE);
2548 else{
2550 * Reset first Other key. Selection screen may have
2551 * blasted it. Do this by hand because menu_init_binding
2552 * will remove the other two OTHER CMDS bindings.
2553 * Should figure out how to do this correctly with a
2554 * reasonable function call.
2556 km->keys[OTHER_KEY].name = "O";
2557 /* TRANSLATORS: This is the name of the command that will show
2558 which other commands are available. 12 commands are shown at
2559 the bottom of the screen, this command would show the next set
2560 of 12 */
2561 km->keys[OTHER_KEY].label = N_("OTHER CMDS");
2562 km->keys[OTHER_KEY].bind.cmd = MC_OTHER;
2563 km->keys[OTHER_KEY].bind.ch[0] = 'O';
2564 km->keys[OTHER_KEY].bind.nch = 1;
2565 KS_OSDATASET(&km->keys[OTHER_KEY], KS_NONE);
2567 km->how_many = 2;
2569 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
2570 if(F_ON(F_ENABLE_AGG_OPS, ps))
2571 km->how_many = 3;
2574 * The TWO_KEY is used as the go back key in
2575 * non-selection mode.
2577 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2578 entry_is_listent(as.top_ent+as.cur_row)){
2579 cmd = MC_UNEXPAND;
2580 menu_init_binding(km, '<', cmd, "<", N_("Unexpand"),
2581 TWO_KEY);
2582 KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
2584 else{
2585 cmd = MC_MAIN;
2586 menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
2587 TWO_KEY);
2588 KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
2591 if(cur_is_open()){
2593 * Add or delete entries from this address book.
2595 /* TRANSLATORS: Add a new entry (this is a command) */
2596 menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"),
2597 ADD_KEY);
2598 menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"),
2599 DELETE_KEY);
2600 /* TRANSLATORS: Compose a message to be sent to the current address
2601 book entry */
2602 menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"),
2603 SENDTO_KEY);
2604 KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER);
2605 menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"),
2606 RCOMPOSE_KEY);
2608 else{
2609 clrbitn(ADD_KEY, bitmap);
2610 clrbitn(DELETE_KEY, bitmap);
2611 clrbitn(SENDTO_KEY, bitmap);
2612 clrbitn(RCOMPOSE_KEY, bitmap);
2613 clrbitn(SAVE_KEY, bitmap);
2614 clrbitn(TAKE_KEY, bitmap);
2615 clrbitn(FORW_KEY, bitmap);
2618 clrbitn(SECONDARY_MAIN_KEY, bitmap);
2620 else if(cur_is_open()){
2621 if(F_ON(F_ENABLE_AGG_OPS, ps))
2622 km->how_many = 3;
2625 * The TWO_KEY is used as the go back key in
2626 * non-selection mode.
2628 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2629 entry_is_listent(as.top_ent+as.cur_row)){
2630 cmd = MC_UNEXPAND;
2631 menu_init_binding(km, '<', cmd, "<", N_("Unexpand"),
2632 TWO_KEY);
2633 KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
2635 else{
2636 if(as.n_addrbk > 1 || as.n_serv){
2637 cmd = MC_POPUP;
2638 menu_init_binding(km, '<', cmd, "<",
2639 N_("AddressBkList"), TWO_KEY);
2640 KS_OSDATASET(&km->keys[TWO_KEY], KS_NONE);
2642 else{
2643 cmd = MC_MAIN;
2644 menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
2645 TWO_KEY);
2646 KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
2650 if(pab->access != NoAccess){
2652 * Add or delete entries from this address book.
2654 menu_init_binding(km, '@', MC_ADD, "@", N_("AddNew"),
2655 ADD_KEY);
2656 menu_init_binding(km, 'D', MC_DELETE, "D", N_("Delete"),
2657 DELETE_KEY);
2659 else{
2660 clrbitn(ADD_KEY, bitmap);
2661 clrbitn(DELETE_KEY, bitmap);
2664 /* Find someplace to put Main Menu command */
2665 if(cmd == MC_POPUP){
2666 menu_init_binding(km, 'M', MC_MAIN, "M", N_("Main Menu"),
2667 SECONDARY_MAIN_KEY);
2668 KS_OSDATASET(&km->keys[SECONDARY_MAIN_KEY],KS_MAINMENU);
2670 else
2671 clrbitn(SECONDARY_MAIN_KEY, bitmap);
2673 menu_init_binding(km, 'C', MC_COMPOSE, "C", N_("ComposeTo"),
2674 SENDTO_KEY);
2675 KS_OSDATASET(&km->keys[SENDTO_KEY], KS_COMPOSER);
2676 menu_init_binding(km, '#', MC_ROLE, "#", N_("Role"),
2677 RCOMPOSE_KEY);
2679 else{
2681 * The TWO_KEY is used as the go back key in
2682 * non-selection mode.
2684 cmd = MC_MAIN;
2685 menu_init_binding(km, 'M', cmd, "<", N_("Main Menu"),
2686 TWO_KEY);
2687 KS_OSDATASET(&km->keys[TWO_KEY], KS_MAINMENU);
2689 clrbitn(SENDTO_KEY, bitmap);
2690 clrbitn(RCOMPOSE_KEY, bitmap);
2691 clrbitn(ADD_KEY, bitmap);
2692 clrbitn(DELETE_KEY, bitmap);
2693 clrbitn(SECONDARY_MAIN_KEY, bitmap);
2694 clrbitn(SAVE_KEY, bitmap);
2695 clrbitn(TAKE_KEY, bitmap);
2696 clrbitn(FORW_KEY, bitmap);
2699 /* can't be on third menu if we just reduced to 2 */
2700 if(km->how_many == 2 && km->which == 2)
2701 km->which = 0;
2703 menu_add_binding(km, '<', cmd);
2704 menu_add_binding(km, ',', cmd);
2705 if(F_ON(F_ARROW_NAV,ps))
2706 menu_add_binding(km, KEY_LEFT, cmd);
2708 KS_OSDATASET(&km->keys[DELETE_KEY], KS_DELETE);
2711 * The THREE_KEY is the burrow into the hierarchy key.
2713 if(entry_is_askserver(as.top_ent+as.cur_row))
2714 cmd = MC_QUERY_SERV;
2715 else if(entry_is_clickable(as.top_ent+as.cur_row)){
2716 if(entry_is_clickable_title(as.top_ent+as.cur_row))
2717 cmd = MC_OPENABOOK;
2718 else
2719 cmd = MC_EXPAND;
2721 else
2722 cmd = MC_VIEW_ENTRY;
2724 menu_init_binding(km, '>', cmd, ">",
2725 cmd == MC_EXPAND ? "[" N_("Expand") "]" :
2726 cmd == MC_QUERY_SERV ? "[" N_("Search") "]" :
2727 /* TRANSLATORS: In the address book the user can
2728 view a particular address book entry. It is
2729 called View/Update because the way to update
2730 an entry is to first view it and then there
2731 will be an opportunity to update it from there. */
2732 cur_is_open() ? "[" N_("View/Update") "]"
2733 : "[" N_("ViewAbook") "]",
2734 THREE_KEY);
2736 if(cmd == MC_QUERY_SERV)
2737 menu_add_binding(km, 's', cmd);
2738 else if(cmd == MC_OPENABOOK || cmd == MC_VIEW_ENTRY)
2739 menu_add_binding(km, 'v', cmd);
2741 menu_add_binding(km, '.', cmd);
2742 if(F_ON(F_ARROW_NAV,ps))
2743 menu_add_binding(km, KEY_RIGHT, cmd);
2746 menu_add_binding(km, ctrl('M'), km->keys[def_key].bind.cmd);
2747 menu_add_binding(km, ctrl('J'), km->keys[def_key].bind.cmd);
2749 if(km_popped){
2750 FOOTER_ROWS(ps) = 3;
2751 clearfooter(ps);
2754 draw_keymenu(km, bitmap, ps->ttyo->screen_cols,
2755 1-FOOTER_ROWS(ps), 0, what);
2756 ps->mangled_footer = 0;
2757 what = SameMenu;
2758 if(km_popped){
2759 FOOTER_ROWS(ps) = 1;
2760 mark_keymenu_dirty();
2764 rdonly = (pab && pab->access == ReadOnly);
2765 empty = is_empty(as.cur_row+as.top_ent);
2767 /*------------ display any status messages ------------------*/
2768 if(km_popped){
2769 FOOTER_ROWS(ps) = 3;
2770 mark_status_unknown();
2773 display_message(c);
2774 if(km_popped){
2775 FOOTER_ROWS(ps) = 1;
2776 mark_status_unknown();
2779 if(F_OFF(F_SHOW_CURSOR, ps)){
2780 /* reset each time through to catch screen size changes */
2781 cursor_pos.row = ps->ttyo->screen_rows-FOOTER_ROWS(ps);
2782 cursor_pos.col = 0;
2785 MoveCursor(cursor_pos.row, cursor_pos.col);
2788 /*---------------- Get command and validate -------------------*/
2789 #ifdef MOUSE
2790 mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);
2791 register_mfunc(mouse_in_content, HEADER_ROWS(ps), 0,
2792 ps->ttyo->screen_rows-(FOOTER_ROWS(ps)+1),
2793 ps->ttyo->screen_cols);
2794 #endif
2796 /* sort out what help needs to be displayed if asked for */
2797 if(as.config)
2798 gAbookHelp = h_abook_config;
2799 else if(are_selecting){
2800 if(cur_is_open()){
2801 if(style == SelectNickTake)
2802 gAbookHelp = h_abook_select_nicks_take;
2803 else if(selecting_one_nick)
2804 gAbookHelp = h_abook_select_nick;
2805 else if(as.checkboxes)
2806 gAbookHelp = h_abook_select_checks;
2807 else if(listmode_ok)
2808 gAbookHelp = h_abook_select_listmode;
2809 else
2810 gAbookHelp = h_abook_select_addr;
2812 else
2813 gAbookHelp = h_abook_select_top;
2815 else
2816 gAbookHelp = cur_is_open() ? h_abook_opened : h_abook_top;
2818 #ifdef _WINDOWS
2819 mswin_setscrollcallback(addr_scroll_callback);
2820 mswin_sethelptextcallback(pcpine_help_addrbook);
2821 #endif
2822 c = READ_COMMAND(&utf8str);
2823 #ifdef MOUSE
2824 clear_mfunc(mouse_in_content);
2825 #endif
2826 #ifdef _WINDOWS
2827 mswin_setscrollcallback(NULL);
2828 mswin_sethelptextcallback(NULL);
2829 #endif
2830 cmd = menu_command(c, km);
2832 dprint((2, "Addrbook command: %d (0x%x %s)\n", cmd,
2833 c, pretty_command(c)));
2835 /* this may be a safe place to update addrbooks */
2836 if(ps->remote_abook_validity > 0 &&
2837 adrbk_check_and_fix_all(ab_nesting_level < 2 &&
2838 !any_ab_open() && checkedn == 0, 0, 0))
2839 ps->mangled_footer = 1;
2841 if(km_popped)
2842 switch(cmd){
2843 case MC_NONE:
2844 case MC_OTHER:
2845 case MC_RESIZE:
2846 case MC_REPAINT:
2847 km_popped++;
2848 break;
2850 default:
2851 clearfooter(ps);
2852 break;
2855 /*------------- execute command ----------------*/
2856 switch(cmd){
2858 /*------------ Noop (new mail check) --------------*/
2859 case MC_NONE:
2860 break;
2863 /*----------- Help -------------------*/
2864 case MC_HELP:
2865 if(FOOTER_ROWS(ps) == 1 && km_popped == 0){
2866 km_popped = 2;
2867 ps->mangled_footer = 1;
2868 break;
2871 if(as.config)
2872 helper(gAbookHelp, _("HELP ON CONFIGURING ADDRESS BOOKS"),
2873 HLPD_NONE);
2874 else if(are_selecting)
2875 helper(gAbookHelp, _("HELP ON ADDRESS BOOK"),
2876 HLPD_SIMPLE | HLPD_NEWWIN);
2877 else /* general maintenance screen */
2878 helper(gAbookHelp, _("HELP ON ADDRESS BOOK"), HLPD_NONE);
2881 * Helper() may have a Main Menu key. If user types that
2882 * they'll set next_screen. We don't have to do anything
2883 * special but we want to make sure that that doesn't happen
2884 * when we're selecting, even though it shouldn't be
2885 * possible because HLPD_SIMPLE is set.
2887 if(are_selecting)
2888 ps->next_screen = SCREEN_FUN_NULL; /* probably not needed */
2890 ps->mangled_screen = 1;
2891 break;
2894 /*---------- display other key bindings ------*/
2895 case MC_OTHER:
2896 warn_other_cmds();
2897 what = NextMenu;
2898 ps->mangled_footer = 1;
2899 break;
2902 /*------------ Unexpand list -----------------*/
2903 case MC_UNEXPAND:
2904 if(F_OFF(F_EXPANDED_DISTLISTS,ps) &&
2905 entry_is_listent(as.top_ent+as.cur_row)){
2906 DL_CACHE_S *dlc_to_flush;
2907 long global_row_num;
2910 * unexpand list
2914 * redraw screen starting with first ListEnt
2916 dl = dlist(as.top_ent+as.cur_row);
2917 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
2920 * New cur_row should be the line after the ListHead line
2921 * that takes the place of the first address in the list.
2923 as.cur_row = as.cur_row - dl->l_offset;
2926 * If the list header for the new row is off the screen,
2927 * adjust it.
2929 if(as.cur_row < 0){ /* center it */
2930 global_row_num = dlc_to_flush->global_row -
2931 dlc_to_flush->dlcoffset;
2932 as.top_ent = first_line(global_row_num - as.l_p_page/2L);
2933 as.cur_row = global_row_num - as.top_ent;
2934 start_disp = 0;
2936 else if(as.cur_row == 0){ /* just slide up in this case */
2937 as.top_ent -= 1;
2938 as.cur_row = 1;
2939 start_disp = 0;
2941 else
2942 start_disp = as.cur_row;
2944 exp_unset_expanded(pab->address_book->exp,
2945 (a_c_arg_t)dlc_to_flush->dlcelnum);
2946 flush_dlc_from_cache(dlc_to_flush);
2947 ps->mangled_body = 1;
2948 ps->mangled_footer = 1;
2950 else
2951 q_status_message(SM_ORDER | SM_DING, 3, 4,
2952 "Can't happen in MC_UNEXPAND");
2954 break;
2956 /*------ Popup to top level display ----------*/
2957 case MC_POPUP:
2958 if(F_ON(F_EXPANDED_DISTLISTS,ps) ||
2959 !entry_is_listent(as.top_ent+as.cur_row)){
2960 DL_CACHE_S dlc_restart;
2961 long new_row;
2963 (void)init_addrbooks((checkedn || as.selections)
2964 ? ThreeQuartOpen : HalfOpen,
2965 0, 0, !are_selecting);
2966 dlc_restart.adrbk_num = as.cur;
2967 dlc_restart.type = DlcTitle;
2968 warp_to_dlc(&dlc_restart, 0L);
2970 * Put the current entry in a nice spot on the screen.
2971 * Will everything above fit and still leave ours on screen?
2973 new_row = LINES_PER_ABOOK * as.cur +
2974 (((pab->type & GLOBAL) &&
2975 (as.how_many_personals > 0 || as.config))
2976 ? XTRA_LINES_BETWEEN : 0) +
2977 ((as.how_many_personals == 0 && as.config)
2978 ? LINES_PER_ADD_LINE : 0);
2979 new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
2981 as.cur_row = new_row;
2982 as.top_ent = 0L - as.cur_row;
2983 start_disp = 0;
2984 ps->mangled_screen = 1;
2986 else
2987 q_status_message(SM_ORDER | SM_DING, 3, 4,
2988 "Can't happen in MC_POPUP");
2990 break;
2993 /*------------- Back to main menu or exit to caller -------*/
2994 case MC_EXIT:
2995 case MC_MAIN:
2996 if(!are_selecting)
2997 ps->next_screen = main_menu_screen;
2999 if(!(are_selecting && as.checkboxes && checkedn > 0)
3000 /* TRANSLATORS: we are asking for confirmation about abandonding selections
3001 in the address book. */
3002 || want_to(_("Really abandon your selections "),
3003 'y', 'x', NO_HELP, WT_NORM) == 'y')
3004 quit++;
3005 else
3006 ps->next_screen = SCREEN_FUN_NULL;
3008 break;
3011 /*------- Open an address book ----------*/
3012 case MC_OPENABOOK:
3013 openabook:
3014 if(entry_is_clickable_title(as.top_ent+as.cur_row)){
3015 DL_CACHE_S *dlc_to_flush;
3017 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3018 if(dlc_to_flush->type == DlcTitle ||
3019 dlc_to_flush->type == DlcClickHereCmb){
3022 * open this addrbook and fill in display list
3025 init_abook(pab, Open);
3027 if(pab->access == ReadWrite || pab->access == ReadOnly){
3028 if(!are_selecting && pab->access == ReadOnly)
3029 q_status_message1(SM_ORDER, 0, 4, _("AddressBook %s is Read Only"), pab->abnick);
3032 * successful open, burrow into addrbook
3034 if(F_OFF(F_CMBND_ABOOK_DISP,ps_global)){
3035 warp_to_top_of_abook(as.cur);
3036 as.top_ent = 0L;
3037 as.cur_row = 0L;
3038 start_disp = 0;
3039 ps->mangled_screen = 1;
3041 else{
3042 flush_dlc_from_cache(dlc_to_flush);
3043 start_disp = as.cur_row;
3044 ps->mangled_footer = 1;
3045 ps->mangled_body = 1;
3048 else{ /* open failed */
3050 * Flush the title line so that it will change into
3051 * a permission denied line,
3053 flush_dlc_from_cache(dlc_to_flush);
3054 start_disp = as.cur_row;
3055 ps->mangled_footer = 1;
3056 ps->mangled_body = 1;
3059 else if(dlc_to_flush->type == DlcTitleNoPerm)
3060 q_status_message(SM_ORDER, 0, 4,
3061 _("Cannot access address book."));
3063 else
3064 q_status_message(SM_ORDER | SM_DING, 3, 4,
3065 "Can't happen in MC_OPENABOOK");
3067 break;
3070 /*------- Expand addresses in List ------*/
3071 case MC_EXPAND:
3072 expand:
3073 if(entry_is_clickable(as.top_ent+as.cur_row)){
3074 DL_CACHE_S *dlc_to_flush;
3076 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3077 if(dlc_to_flush->type == DlcListClickHere){
3078 start_disp = as.cur_row; /* will redraw from here down */
3081 * Mark this list expanded, then flush the
3082 * current line from dlc cache. When we get the
3083 * line again it will notice the expanded flag and change
3084 * the type to DlcListEnt (if any entries).
3087 if(F_OFF(F_EXPANDED_DISTLISTS,ps))
3088 exp_set_expanded(pab->address_book->exp,
3089 (a_c_arg_t)dlc_to_flush->dlcelnum);
3091 flush_dlc_from_cache(dlc_to_flush);
3092 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3094 * If list is empty, back cursor up one.
3096 if(dlc_to_flush->type == DlcListEmpty){
3097 as.cur_row--;
3098 start_disp--;
3099 if(as.cur_row < 0){
3100 as.top_ent--;
3101 as.cur_row = 0;
3102 start_disp = 0;
3106 ps->mangled_body = 1;
3109 else
3110 q_status_message(SM_ORDER | SM_DING, 3, 4,
3111 "Can't happen in MC_EXPAND");
3113 break;
3116 /*------- Select ---------------*/
3117 case MC_CHOICE:
3118 select:
3119 if(are_selecting){
3120 /* Select an entry to mail to or a nickname to add to */
3121 if(!any_addrs_avail(as.top_ent+as.cur_row)){
3122 q_status_message(SM_ORDER | SM_DING, 0, 4,
3123 _("No entries in address book. Use ExitSelect to leave address books"));
3124 break;
3127 if(as.checkboxes || is_addr(as.top_ent+as.cur_row)){
3128 BuildTo bldto;
3129 char *to = NULL;
3130 char *error = NULL;
3131 AdrBk_Entry *abe;
3133 dl = dlist(as.top_ent+as.cur_row);
3135 if(selecting_one_nick){
3136 char nickbuf[MAX_NICKNAME + 1];
3138 strncpy(nickbuf,
3139 ae(as.top_ent+as.cur_row)->nickname,
3140 sizeof(nickbuf)-1);
3141 nickbuf[sizeof(nickbuf)-1] = '\0';
3142 return(cpystr(nickbuf));
3144 else if(as.checkboxes && checkedn <= 0){
3145 q_status_message(SM_ORDER, 0, 1,
3146 _("Use \"X\" to mark addresses or lists"));
3147 break;
3149 else if(as.checkboxes){
3150 size_t incr = 100, avail, alloced;
3153 * Have to run through all of the checked entries
3154 * in all of the address books.
3155 * Put the nicknames together into one long
3156 * string with comma separators and let
3157 * our_build_address handle the parsing.
3159 to = (char *)fs_get(incr);
3160 *to = '\0';
3161 avail = incr;
3162 alloced = incr;
3164 for(i = 0; i < as.n_addrbk; i++){
3165 EXPANDED_S *next_one;
3166 adrbk_cntr_t num;
3167 AddrScrn_Disp fake_dl;
3168 char *a_string;
3170 pab = &as.adrbks[i];
3171 if(pab->address_book)
3172 next_one = pab->address_book->checks;
3173 else
3174 continue;
3176 while((num = entry_get_next(&next_one)) != NO_NEXT){
3177 abe = adrbk_get_ae(pab->address_book,
3178 (a_c_arg_t) num);
3180 * Since we're picking up address book entries
3181 * directly from the address books and have
3182 * no knowledge of the display lines they came
3183 * from, we don't know the dl's that go with
3184 * them. We need to pass a dl to abe_to_nick
3185 * but it really is only going to use the
3186 * type in this case.
3188 dl = &fake_dl;
3189 dl->type = (abe->tag == Single) ? Simple
3190 : ListHead;
3191 a_string = abe_to_nick_or_addr_string(abe, dl, i);
3193 while(abe && avail < (size_t)strlen(a_string)+1){
3194 alloced += incr;
3195 avail += incr;
3196 fs_resize((void **)&to, alloced);
3199 if(!*to){
3200 strncpy(to, a_string, alloced);
3201 to[alloced-1] = '\0';
3203 else{
3204 strncat(to, ",", alloced-strlen(to)-1);
3205 strncat(to, a_string, alloced-strlen(to)-1);
3208 avail -= (strlen(a_string) + 1);
3213 * Return the nickname list for lcc so that the
3214 * correct fullname can make it to the To line.
3215 * If we expand it ahead of time, the list name
3216 * and first user's fullname will get mushed together.
3217 * If an entry doesn't have a nickname then we're
3218 * out of luck as far as getting the right entry
3219 * in the To line goes.
3221 if(selecting_mult_nicks)
3222 return(to);
3224 bldto.type = Str;
3225 bldto.arg.str = to;
3227 else{
3228 /* Select an address, but not using checkboxes */
3229 if(selecting_mult_nicks){
3230 if(dl->type != ListHead && style == SelectAddrLccCom){
3231 q_status_message(SM_ORDER, 0, 4,
3232 _("You may only select lists for Lcc, use Bcc for other addresses"));
3233 break;
3235 else{
3237 * Even though we're supposedly selecting
3238 * nicknames, we have a special case here to
3239 * select a single member of a distribution
3240 * list. This happens with style SelectNicksCom
3241 * which is the regular ^T entry from the
3242 * composer, and it allows somebody to mail to
3243 * a single member of a distribution list.
3245 abe = ae(as.top_ent+as.cur_row);
3246 return(abe_to_nick_or_addr_string(abe, dl, as.cur));
3249 else{
3250 if(dl->type == ListEnt){
3251 bldto.type = Str;
3252 bldto.arg.str =
3253 listmem_from_dl(pab->address_book, dl);
3255 else{
3256 bldto.type = Abe;
3257 bldto.arg.abe = ae(as.top_ent+as.cur_row);
3262 (void)our_build_address(bldto, &addr, &error, NULL, save_and_restore);
3263 /* Have to rfc1522_decode the addr */
3264 if(addr){
3265 char *tmp_a_string, *p;
3266 ADDRESS *a = NULL;
3268 if(style == SelectAddrNoFull){
3269 tmp_a_string = cpystr(addr);
3270 rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain);
3271 fs_give((void **)&tmp_a_string);
3272 if(a){
3273 fs_give((void **)&addr);
3274 addr = cpystr(simple_addr_string(a, tmp_20k_buf,
3275 SIZEOF_20KBUF));
3276 mail_free_address(&a);
3279 else if(style == SelectMultNoFull){
3280 tmp_a_string = cpystr(addr);
3281 rfc822_parse_adrlist(&a,tmp_a_string,ps->maildomain);
3282 fs_give((void **)&tmp_a_string);
3283 if(a){
3284 fs_give((void **)&addr);
3285 addr = cpystr(simple_mult_addr_string(a,
3286 tmp_20k_buf,
3287 SIZEOF_20KBUF,
3288 ","));
3289 mail_free_address(&a);
3292 else{
3293 size_t len;
3294 len = 4*strlen(addr)+1;
3295 p = (char *)fs_get(len * sizeof(char));
3296 if(rfc1522_decode_to_utf8((unsigned char *)p, len, addr) == (unsigned char *)p){
3297 fs_give((void **)&addr);
3298 addr = p;
3300 else
3301 fs_give((void **)&p);
3305 if(to)
3306 fs_give((void **)&to);
3308 if(error){
3309 q_status_message1(SM_ORDER, 3, 4, "%s", error);
3310 fs_give((void **)&error);
3313 return(addr); /* Caller frees this */
3315 else{
3316 if(entry_is_clickable(as.top_ent+as.cur_row))
3317 clickable_warning(as.top_ent+as.cur_row);
3318 else if(entry_is_askserver(as.top_ent+as.cur_row))
3319 q_status_message(SM_ORDER, 3, 4, _("Use select to select an address or addresses from address books"));
3320 else
3321 q_status_message(SM_ORDER, 3, 4, _("No address selected"));
3323 break;
3326 else
3327 q_status_message(SM_ORDER | SM_DING, 3, 4,
3328 "Can't happen in MC_CHOICE");
3330 break;
3333 /*----- View an entry --------------------*/
3334 case MC_VIEW_ENTRY:
3335 view:
3336 if(empty){
3337 empty_warning(as.top_ent+as.cur_row);
3338 break;
3341 view_abook_entry(ps, as.top_ent+as.cur_row);
3342 break;
3345 /*----- Add new ---------*/
3346 case MC_ADD:
3347 {long old_l_p_p, old_top_ent, old_cur_row;
3349 if(adrbk_check_all_validity_now()){
3350 if(resync_screen(pab, style, checkedn)){
3351 q_status_message(SM_ORDER | SM_DING, 3, 4,
3352 _("Address book changed. AddNew cancelled. Try again."));
3353 ps->mangled_screen = 1;
3354 break;
3358 if(rdonly){
3359 q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
3360 break;
3363 warped = 0;
3364 dprint((9,
3365 "Calling edit_entry to add entry manually\n"));
3366 /* TRANSLATORS: add as in add a new entry to something */
3367 edit_entry(pab->address_book, (AdrBk_Entry *)NULL, NO_NEXT,
3368 NotSet, 0, &warped, _("add"));
3371 * Warped means we got plopped down somewhere in the display
3372 * list so that we don't know where we are relative to where
3373 * we were before we warped. The current line number will
3374 * be zero, since that is what the warp would have set.
3376 if(warped){
3377 as.top_ent = first_line(0L - as.l_p_page/2L);
3378 as.cur_row = 0L - as.top_ent;
3380 else{
3382 * If we didn't warp, that means we didn't change at all,
3383 * so keep old screen.
3385 old_l_p_p = as.l_p_page;
3386 old_top_ent = as.top_ent;
3387 old_cur_row = as.cur_row;
3390 /* Window size may have changed while in pico. */
3391 ab_resize();
3393 /* fix up what ab_resize messed up */
3394 if(!warped && old_l_p_p == as.l_p_page){
3395 as.top_ent = old_top_ent;
3396 as.cur_row = old_cur_row;
3397 as.old_cur_row = old_cur_row;
3401 ps->mangled_screen = 1;
3402 break;
3405 /*---------- Add a new address book -------------------*/
3406 case MC_ADDABOOK:
3407 {int old_abook_num, new_abook_num, new_row, global;
3408 int stay_put, old_cur_row;
3410 add_abook:
3411 stay_put = (dlist(as.top_ent+as.cur_row)->type == AddFirstPers ||
3412 dlist(as.top_ent+as.cur_row)->type == AddFirstGlob);
3413 old_cur_row = as.cur_row;
3414 old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
3415 global = (dlist(as.top_ent+as.cur_row)->type == AddFirstGlob ||
3416 (dlist(as.top_ent+as.cur_row)->type != AddFirstPers &&
3417 (old_abook_num >= as.how_many_personals) &&
3418 as.n_addrbk > 0));
3419 if((new_abook_num =
3420 ab_add_abook(global,
3421 stay_put ? -1
3422 : adrbk_num_from_lineno(as.top_ent+as.cur_row)))
3423 >= 0){
3424 DL_CACHE_S dlc_restart;
3426 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3428 erase_checks();
3429 erase_selections();
3431 dlc_restart.adrbk_num = new_abook_num;
3432 dlc_restart.type = DlcTitle;
3433 warp_to_dlc(&dlc_restart, 0L);
3436 * Put the current entry in a nice spot on the screen.
3438 new_row = old_cur_row +
3439 (stay_put
3441 : LINES_PER_ABOOK*(new_abook_num - old_abook_num));
3443 if(new_row >= 0 &&
3444 new_row <= (as.l_p_page-VIS_LINES_PER_ABOOK))/* ok, use it */
3445 as.cur_row = new_row;
3446 else{
3448 * Will everything above fit and still leave ours on screen?
3450 new_row = LINES_PER_ABOOK * new_abook_num +
3451 ((global &&
3452 (as.how_many_personals > 0 || as.config))
3453 ? XTRA_LINES_BETWEEN : 0) +
3454 ((as.how_many_personals == 0 && as.config)
3455 ? LINES_PER_ADD_LINE : 0);
3456 new_row = MAX(MIN(new_row,
3457 as.l_p_page - VIS_LINES_PER_ABOOK), 0);
3458 as.cur_row = new_row;
3461 as.top_ent = 0L - as.cur_row;
3462 dprint((5, "addrbook added: %s\n",
3463 as.adrbks[new_abook_num].filename
3464 ? as.adrbks[new_abook_num].filename : "?"));
3467 ps->mangled_screen = 1;
3471 break;
3474 /*---------- Change address book config -------------------*/
3475 case MC_EDITABOOK:
3476 change_abook:
3477 if(entry_is_clickable_title(as.top_ent+as.cur_row)){
3478 int abook_num, old_cur_row, global;
3479 long old_top_ent;
3480 DL_CACHE_S dlc_restart, *dlc;
3481 char *serv = NULL, *folder = NULL, *p, *q;
3482 char *nick = NULL, *file = NULL;
3484 abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
3485 global = (abook_num >= as.how_many_personals);
3486 old_cur_row = as.cur_row;
3487 old_top_ent = as.top_ent;
3488 dlc = get_dlc(as.top_ent+as.cur_row);
3489 dlc_restart = *dlc;
3491 if(global)
3492 q = ps_global->VAR_GLOB_ADDRBOOK[abook_num -
3493 as.how_many_personals];
3494 else
3495 q = ps_global->VAR_ADDRESSBOOK[abook_num];
3497 get_pair(q, &nick, &file, 0, 0);
3499 if(nick && !*nick)
3500 fs_give((void **)&nick);
3502 if(file && *file == '{'){
3503 q = file + 1;
3504 if((p = strindex(file, '}'))){
3505 *p = '\0';
3506 serv = q;
3507 folder = p+1;
3509 else{
3510 q_status_message1(SM_ORDER|SM_DING, 0, 4,
3511 _("Missing \"}\" in config: %s"), q);
3512 if(nick)
3513 fs_give((void **)&nick);
3514 if(file)
3515 fs_give((void **)&file);
3517 break;
3520 else
3521 folder = file;
3523 if(ab_edit_abook(global, abook_num, serv, folder, nick) >= 0){
3524 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3526 erase_checks();
3527 erase_selections();
3529 dlc_restart.type = DlcTitle;
3530 warp_to_dlc(&dlc_restart, old_top_ent+(long)old_cur_row);
3531 as.cur_row = old_cur_row;
3532 as.top_ent = old_top_ent;
3534 dprint((5, "addrbook config edited: %s\n",
3535 as.adrbks[dlc_restart.adrbk_num].filename
3536 ? as.adrbks[dlc_restart.adrbk_num].filename : "?"));
3539 if(nick)
3540 fs_give((void **)&nick);
3541 if(file)
3542 fs_give((void **)&file);
3544 ps->mangled_screen = 1;
3546 else
3547 /* TRANSLATORS: the user tried to change the current line of the address
3548 book but the line could not be changed */
3549 q_status_message(SM_ORDER, 0, 4, _("Not a changeable line"));
3551 break;
3554 /*---------- Delete an address book -------------------*/
3555 case MC_DELABOOK:
3556 if(as.n_addrbk == 0){
3557 q_status_message(SM_ORDER, 0, 4, _("Nothing to delete"));
3558 break;
3561 {char *err = NULL;
3563 if(ab_del_abook(as.top_ent+as.cur_row, command_line, &err) >= 0){
3564 DL_CACHE_S dlc_restart;
3565 int new_abook_num, old_abook_num, old_pers, old_glob, new_row;
3568 * Careful, these are only ok because ab_del_abook didn't
3569 * mess with the as globals, like as.how_many_personals.
3570 * The addrbook_reset does reset them, of course.
3572 old_abook_num = adrbk_num_from_lineno(as.top_ent+as.cur_row);
3573 old_pers = as.how_many_personals;
3574 old_glob = as.n_addrbk - as.how_many_personals;
3575 addrbook_reset();
3576 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3578 erase_checks();
3579 erase_selections();
3581 if(old_abook_num >= as.n_addrbk) /* we deleted last addrbook */
3582 new_abook_num = as.n_addrbk - 1;
3583 else
3584 new_abook_num = old_abook_num;
3587 * Pick a line to highlight and center
3589 if(as.how_many_personals == 0 && old_pers == 1)
3590 dlc_restart.type = DlcPersAdd;
3591 else if((as.n_addrbk - as.how_many_personals) == 0 &&
3592 old_glob == 1)
3593 dlc_restart.type = DlcGlobAdd;
3594 else if(as.n_addrbk == 0)
3595 dlc_restart.type = DlcPersAdd;
3596 else{
3597 dlc_restart.adrbk_num = new_abook_num;
3598 dlc_restart.type = DlcTitle;
3601 warp_to_dlc(&dlc_restart, 0L);
3604 * Will everything above fit and still leave ours on screen?
3606 if(dlc_restart.type == DlcTitle)
3607 new_row = LINES_PER_ABOOK * new_abook_num +
3608 (((as.adrbks[new_abook_num].type & GLOBAL) &&
3609 (as.how_many_personals > 0 || as.config))
3610 ? XTRA_LINES_BETWEEN : 0) +
3611 ((as.how_many_personals == 0 && as.config)
3612 ? LINES_PER_ADD_LINE : 0);
3613 else if(dlc_restart.type == DlcGlobAdd)
3614 new_row = LINES_PER_ABOOK * as.n_addrbk +
3615 ((as.how_many_personals > 0 || as.config)
3616 ? XTRA_LINES_BETWEEN : 0) +
3617 ((as.how_many_personals == 0 && as.config)
3618 ? LINES_PER_ADD_LINE : 0);
3619 else
3620 new_row = 0;
3622 new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
3623 as.cur_row = new_row;
3624 as.top_ent = 0L - as.cur_row;
3625 start_disp = 0;
3626 ps->mangled_body = 1;
3627 ps->mangled_footer = 1;
3628 /* TRANSLATORS: This is just comforting confirmation that the
3629 address book being deleted was successfully deleted */
3630 q_status_message(SM_ORDER, 0, 3, _("Address book deleted"));
3632 else{
3633 if(err){
3634 q_status_message(SM_ORDER, 0, 4, err);
3635 dprint((5, "addrbook delete failed: %s\n",
3636 err ? err : "?"));
3642 break;
3645 /*---- Reorder an addressbook list ---------*/
3646 case MC_SHUFFLE:
3647 if(entry_is_addkey(as.top_ent+as.cur_row)){
3648 q_status_message(SM_ORDER, 0, 4,
3649 _("Highlight entry you wish to shuffle"));
3650 break;
3653 {int slide;
3654 char *msg = NULL;
3655 int ret, new_anum;
3657 if((ret = ab_shuffle(pab, &slide, command_line, &msg)) > 0){
3658 DL_CACHE_S dlc_restart;
3659 int new_row;
3661 new_anum = ret - 1; /* see ab_shuffle return value */
3662 addrbook_reset();
3663 (void)init_addrbooks(HalfOpen, 0, 0, !are_selecting);
3665 erase_checks();
3666 erase_selections();
3668 /* put cursor on new_anum */
3669 dlc_restart.adrbk_num = new_anum;
3670 dlc_restart.type = DlcTitle;
3671 warp_to_dlc(&dlc_restart, 0L);
3673 * Will everything above fit and still leave ours on screen?
3675 new_row = LINES_PER_ABOOK * new_anum +
3676 (((as.adrbks[new_anum].type & GLOBAL) &&
3677 (as.how_many_personals > 0 || as.config))
3678 ? XTRA_LINES_BETWEEN : 0) +
3679 ((as.how_many_personals == 0 && as.config)
3680 ? LINES_PER_ADD_LINE : 0);
3681 new_row = MAX(MIN(new_row, as.l_p_page-VIS_LINES_PER_ABOOK), 0);
3682 as.cur_row = new_row;
3683 as.top_ent = 0L - as.cur_row;
3684 start_disp = 0;
3685 ps->mangled_body = 1;
3687 else if(ret == 0){
3688 DL_CACHE_S *dlc_to_flush;
3690 if(slide < 0){ /* moved it up */
3691 as.cur_row += slide;
3692 start_disp = MAX(as.cur_row - 1, 0);
3693 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3695 * If we backed off the top of the screen, just
3696 * inch the display up so we can see it.
3698 if(as.cur_row < 0){
3699 as.top_ent += as.cur_row; /* cur_row is negative */
3700 as.cur_row = 0;
3701 start_disp = 0;
3704 else{ /* moved it down */
3705 start_disp = as.cur_row;
3706 dlc_to_flush = get_dlc(as.top_ent+as.cur_row);
3707 as.cur_row += slide;
3708 if(as.cur_row > as.l_p_page - VIS_LINES_PER_ABOOK){
3709 as.top_ent += (as.cur_row -
3710 (as.l_p_page - VIS_LINES_PER_ABOOK));
3711 as.cur_row = MAX(as.l_p_page-VIS_LINES_PER_ABOOK, 0);
3712 start_disp = 0;
3716 flush_dlc_from_cache(dlc_to_flush);
3717 ps->mangled_body = 1;
3720 q_status_message(SM_ORDER, 0, 3,
3721 msg ? msg :
3722 (ret < 0) ? _("Shuffle failed") :
3723 _("Address books shuffled"));
3724 if(ret < 0)
3725 dprint((5, "addrbook shuffle failed: %s\n",
3726 msg ? msg : "?"));
3728 if(msg)
3729 fs_give((void **)&msg);
3733 break;
3736 /*----------------------- Move Up ---------------------*/
3737 case MC_CHARUP:
3738 case MC_PREVITEM:
3739 r = prev_selectable_line(as.cur_row+as.top_ent, &new_line);
3740 if(r == 0){
3741 /* find first line so everything is displayed */
3742 new_top_ent = as.cur_row+as.top_ent;
3743 for(dl=dlist(new_top_ent-1);
3744 dl->type != Beginning;
3745 dl = dlist((--new_top_ent) - 1))
3748 if(new_top_ent == as.top_ent ||
3749 (as.cur_row + (as.top_ent-new_top_ent) > as.l_p_page - 1)){
3750 q_status_message(SM_INFO, 0, 1, _("Already on first line."));
3752 else{
3753 as.cur_row += (as.top_ent - new_top_ent);
3754 as.top_ent = new_top_ent;
3755 start_disp = 0;
3756 ps->mangled_body = 1;
3759 break;
3762 i = ((cmd == MC_CHARUP)
3763 && dlist(as.top_ent - 1L)->type != Beginning)
3764 ? MIN(HS_MARGIN(ps), as.cur_row) : 0;
3765 as.cur_row = new_line - as.top_ent;
3766 if(as.cur_row - i < 0){
3767 if(cmd == MC_CHARUP){
3768 /*-- Past top of page --*/
3769 as.top_ent += (as.cur_row - i);
3770 as.cur_row = i;
3772 else{
3773 new_top_ent = first_line(as.top_ent - as.l_p_page);
3774 as.cur_row += (as.top_ent - new_top_ent);
3775 as.top_ent = new_top_ent;
3776 /* if it is still off screen */
3777 if(as.cur_row - i < 0){
3778 as.top_ent += (as.cur_row - i);
3779 as.cur_row = i;
3783 start_disp = 0;
3784 ps->mangled_body = 1;
3786 else
3787 current_changed_flag++;
3789 break;
3792 /*------------------- Move Down -------------------*/
3793 case MC_CHARDOWN:
3794 case MC_NEXTITEM:
3795 r = next_selectable_line(as.cur_row+as.top_ent, &new_line);
3796 if(r == 0){
3797 long new_end_line;
3799 /* find last line so everything is displayed */
3800 new_end_line = as.cur_row+as.top_ent;
3801 for(dl=dlist(new_end_line+1);
3802 dl->type != End;
3803 dl = dlist((++new_end_line)+1))
3806 if(new_end_line - as.top_ent <= as.l_p_page - 1 ||
3807 as.cur_row - (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){
3808 q_status_message(SM_INFO, 0, 1, _("Already on last line."));
3810 else{
3811 as.cur_row -= (new_end_line-as.top_ent-(as.l_p_page-1));
3812 as.top_ent += (new_end_line-as.top_ent-(as.l_p_page-1));
3813 start_disp = 0;
3814 ps->mangled_body = 1;
3817 break;
3820 /* adjust for scrolling margin */
3821 i = (cmd == MC_CHARDOWN) ? HS_MARGIN(ps) : 0;
3822 as.cur_row = new_line - as.top_ent;
3823 if(as.cur_row >= as.l_p_page - i){
3824 if(cmd == MC_CHARDOWN){
3825 /*-- Past bottom of page --*/
3826 as.top_ent += (as.cur_row - (as.l_p_page - i) + 1);
3827 as.cur_row = (as.l_p_page - i) - 1;
3829 else{
3830 /*-- Changed pages --*/
3831 as.top_ent += as.l_p_page;
3832 as.cur_row -= as.l_p_page;
3833 /* if it is still off screen */
3834 if(as.cur_row >= as.l_p_page - i){
3835 as.top_ent += (as.cur_row - (as.l_p_page - i) + 1);
3836 as.cur_row = (as.l_p_page - i) - 1;
3840 start_disp = 0;
3841 ps->mangled_body = 1;
3843 else
3844 current_changed_flag++;
3846 break;
3849 #ifdef MOUSE
3850 case MC_MOUSE:
3852 MOUSEPRESS mp;
3855 * Get the mouse down. Convert to content row number.
3856 * If the row is selectable, do the single or double click
3857 * operation.
3859 mouse_get_last(NULL, &mp);
3860 mp.row -= HEADER_ROWS(ps);
3861 if(line_is_selectable(as.top_ent + mp.row)){
3862 if(mp.button == M_BUTTON_LEFT){
3863 if(mp.doubleclick){
3865 * A mouse double click does the default action to
3866 * the selected line. Since we need to do a goto
3867 * to get there, we have this ugly bit of code.
3869 * On the first mouse click we go around the loop
3870 * once and set def_key appropriately for the
3871 * line as.top_ent+mp.row, which will then be
3872 * the same as as.top_ent+as.cur_row.
3874 switch(km->keys[def_key].bind.cmd){
3875 case MC_CHOICE:
3876 if (as.checkboxes) goto togglex;
3877 else goto select;
3878 case MC_OPENABOOK:
3879 goto openabook;
3880 case MC_EXPAND:
3881 goto expand;
3882 case MC_TOGGLE:
3883 goto togglex;
3884 case MC_SELALL:
3885 goto selall;
3886 case MC_VIEW_ENTRY:
3887 goto view;
3888 case MC_ADDABOOK:
3889 goto add_abook;
3890 case MC_EDITABOOK:
3891 goto change_abook;
3892 #ifdef ENABLE_LDAP
3893 case MC_QUERY_SERV:
3894 goto q_server;
3895 #endif
3896 default:
3897 q_status_message(SM_INFO, 0, 1,
3898 "Can't happen in MC_MOUSE");
3899 break;
3903 as.cur_row = mp.row;
3904 current_changed_flag++;
3906 else if(mp.button == M_BUTTON_RIGHT){
3907 #ifdef _WINDOWS
3908 int need_redraw;
3909 #endif
3910 as.cur_row = mp.row;
3911 current_changed_flag++;
3914 #ifdef _WINDOWS
3915 need_redraw = calculate_field_widths();
3917 /*---------- Update the current entry, (move or change) -------*/
3918 display_book(need_redraw ? 0 : as.cur_row,
3919 as.cur_row,
3920 as.old_cur_row,
3921 need_redraw,
3922 &cursor_pos);
3924 as.old_cur_row = as.cur_row;
3925 current_changed_flag = 0;
3926 as.cur = cur_addr_book();
3927 pab = (as.n_addrbk &&
3928 !entry_is_askserver(as.top_ent+as.cur_row))
3929 ? &as.adrbks[as.cur] : NULL;
3931 if(!mp.doubleclick){
3932 MPopup addr_pu[20];
3933 int n, i;
3935 /* Make what they clicked "current" */
3936 memset(addr_pu, 0, 20 * sizeof(MPopup));
3938 addr_pu[n = 0].type = tQueue;
3939 addr_pu[n].data.val = km->keys[def_key].bind.ch[0];
3940 addr_pu[n].label.style = lNormal;
3941 addr_pu[n++].label.string = km->keys[def_key].label;
3943 if((i = menu_clear_binding(km, '<')) != MC_UNKNOWN){
3944 menu_add_binding(km, '<', i);
3946 if((i = menu_binding_index(km, i)) >= 0){
3947 addr_pu[n++].type = tSeparator;
3949 addr_pu[n].type = tQueue;
3950 addr_pu[n].data.val = km->keys[i].bind.ch[0];
3951 addr_pu[n].label.style = lNormal;
3952 addr_pu[n++].label.string = km->keys[i].label;
3956 addr_pu[n].type = tTail;
3958 mswin_popup(addr_pu);
3960 #endif
3964 break;
3965 #endif
3968 /*------------- Page Up or Down --------*/
3969 case MC_PAGEUP:
3970 case MC_PAGEDN:
3971 if(cmd == MC_PAGEUP){
3972 /* find first line on prev page */
3973 new_top_ent = first_line(as.top_ent - as.l_p_page);
3974 if(new_top_ent == NO_LINE)
3975 break;
3977 /* find first selectable line */
3978 fl = first_selectable_line(new_top_ent);
3980 /* If we didn't move, we'd better move now */
3981 if(fl == as.top_ent+as.cur_row){
3982 if(!prev_selectable_line(as.cur_row+as.top_ent, &new_line)){
3983 long lineno;
3985 /* find first line so everything is displayed */
3986 lineno = as.cur_row+as.top_ent;
3987 for(dl=dlist(lineno);
3988 dl->type != Beginning;
3989 dl = dlist(--lineno))
3993 * If this new_top_ent is the same as the old_top_ent
3994 * we'll get the warning message.
3996 new_top_ent = first_line(lineno);
3997 if(fl - new_top_ent >= as.l_p_page)
3998 new_top_ent += (fl - new_top_ent - as.l_p_page + 1);
4000 else
4001 fl = new_line;
4004 if(fl == NO_LINE)
4005 break;
4007 if(as.top_ent == new_top_ent && as.cur_row == (fl-as.top_ent)){
4008 q_status_message(SM_INFO, 0, 1, _("Already on first page."));
4009 break;
4012 if(as.top_ent == new_top_ent)
4013 current_changed_flag++;
4014 else
4015 as.top_ent = new_top_ent;
4017 else{ /* Down */
4018 /* find first selectable line on next page */
4019 fl = first_selectable_line(as.top_ent + as.l_p_page);
4020 if(fl == NO_LINE)
4021 break;
4023 /* if there is another page, scroll */
4024 if(fl - as.top_ent >= as.l_p_page){
4025 new_top_ent = as.top_ent + as.l_p_page;
4027 /* on last page already */
4028 else{
4029 new_top_ent = as.top_ent;
4030 if(as.cur_row == (fl - as.top_ent)){ /* no change */
4031 long new_end_line;
4033 /* find last line so everything is displayed */
4034 new_end_line = as.cur_row+as.top_ent;
4035 for(dl=dlist(new_end_line+1);
4036 dl->type != End;
4037 dl = dlist((++new_end_line)+1))
4040 if(new_end_line - as.top_ent <= as.l_p_page - 1 ||
4041 as.cur_row -
4042 (new_end_line-as.top_ent-(as.l_p_page-1)) < 0){
4043 q_status_message(SM_INFO, 0, 1,
4044 _("Already on last page."));
4046 else{
4047 as.cur_row -=
4048 (new_end_line-as.top_ent-(as.l_p_page-1));
4049 as.top_ent +=
4050 (new_end_line-as.top_ent-(as.l_p_page-1));
4051 start_disp = 0;
4052 ps->mangled_body = 1;
4055 break;
4059 if(as.top_ent == new_top_ent)
4060 current_changed_flag++;
4061 else
4062 as.top_ent = new_top_ent;
4066 * Stuff in common for up or down.
4068 as.cur_row = fl - as.top_ent;
4070 /* if it is still off screen */
4071 if(as.cur_row < 0){
4072 as.top_ent += as.cur_row;
4073 as.cur_row = 0;
4075 else if(as.cur_row >= as.l_p_page){
4076 as.top_ent += (as.cur_row - as.l_p_page + 1);
4077 as.cur_row = as.l_p_page - 1;
4080 if(!current_changed_flag){
4081 ps->mangled_body = 1;
4082 start_disp = 0;
4085 break;
4088 /*------------- Delete item from addrbook ---------*/
4089 case MC_DELETE:
4090 if(adrbk_check_all_validity_now()){
4091 if(resync_screen(pab, style, checkedn)){
4092 q_status_message(SM_ORDER | SM_DING, 3, 4,
4093 _("Address book changed. Delete cancelled. Try again."));
4094 ps->mangled_screen = 1;
4095 break;
4099 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4100 q_status_message(SM_ORDER, 0, 4, _("No entries to delete"));
4101 break;
4104 if(entry_is_clickable(as.top_ent+as.cur_row)){
4105 clickable_warning(as.top_ent+as.cur_row);
4106 break;
4109 if(rdonly){
4110 q_status_message(SM_ORDER, 0, 4, _("AddressBook is Read Only"));
4111 break;
4114 if(empty){
4115 empty_warning(as.top_ent+as.cur_row);
4116 break;
4119 warped = 0;
4120 did_delete = single_entry_delete(pab->address_book,
4121 as.cur_row+as.top_ent,
4122 &warped);
4123 ps->mangled_footer = 1;
4124 if(did_delete){
4125 if(warped){
4126 as.top_ent = first_line(0L - as.l_p_page/2L);
4127 as.cur_row = 0L - as.top_ent;
4128 start_disp = 0;
4130 else{
4132 * In case the line we're now at is not a selectable
4133 * field.
4135 new_line = first_selectable_line(as.cur_row+as.top_ent);
4136 if(new_line != NO_LINE
4137 && new_line != as.cur_row+as.top_ent){
4138 as.cur_row = new_line - as.top_ent;
4139 if(as.cur_row < 0){
4140 as.top_ent -= as.l_p_page;
4141 as.cur_row += as.l_p_page;
4143 else if(as.cur_row >= as.l_p_page){
4144 as.top_ent += as.l_p_page;
4145 as.cur_row -= as.l_p_page;
4149 start_disp = MIN(as.cur_row, as.old_cur_row);
4152 ps->mangled_body = 1;
4155 break;
4158 /*------------- Toggle checkbox ---------*/
4159 case MC_TOGGLE:
4160 togglex:
4161 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4162 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4163 break;
4166 if(entry_is_clickable(as.top_ent+as.cur_row)){
4167 clickable_warning(as.top_ent+as.cur_row);
4168 break;
4171 if(empty){
4172 empty_warning(as.top_ent+as.cur_row);
4173 break;
4176 if(is_addr(as.top_ent+as.cur_row)){
4177 dl = dlist(as.top_ent+as.cur_row);
4179 if(style == SelectAddrLccCom && dl->type == ListEnt)
4180 q_status_message(SM_ORDER, 0, 4,
4181 _("You may only select whole lists for Lcc"));
4182 else if(style == SelectAddrLccCom && dl->type != ListHead)
4183 q_status_message(SM_ORDER, 0, 4,
4184 _("You may only select lists for Lcc, use Bcc for personal entries"));
4185 else if(dl->type == ListHead || dl->type == Simple){
4186 current_changed_flag++;
4187 if(entry_is_checked(pab->address_book->checks,
4188 (a_c_arg_t)dl->elnum)){
4189 entry_unset_checked(pab->address_book->checks,
4190 (a_c_arg_t)dl->elnum);
4191 checkedn--;
4192 if(checkedn == 0)
4193 setall_changed++;
4195 else{
4196 entry_set_checked(pab->address_book->checks,
4197 (a_c_arg_t)dl->elnum);
4198 if(checkedn == 0)
4199 setall_changed++;
4201 checkedn++;
4204 else
4205 q_status_message(SM_ORDER, 0, 4,
4206 _("You may not select list members, only whole lists or personal entries"));
4208 else
4209 q_status_message(SM_ORDER, 0, 4,
4210 _("You may only select addresses or lists"));
4212 break;
4215 /*------ Turn all checkboxes on ---------*/
4216 case MC_SELALL:
4217 selall:
4218 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4219 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4220 break;
4223 if(entry_is_clickable(as.top_ent+as.cur_row)){
4224 clickable_warning(as.top_ent+as.cur_row);
4225 break;
4228 if(empty){
4229 empty_warning(as.top_ent+as.cur_row);
4230 break;
4234 adrbk_cntr_t num, ab_count;
4236 ab_count = adrbk_count(pab->address_book);
4237 setall_changed++;
4238 if(checkedn){ /* unset All */
4239 for(num = 0; num < ab_count; num++){
4240 if(entry_is_checked(pab->address_book->checks,
4241 (a_c_arg_t)num)){
4242 entry_unset_checked(pab->address_book->checks,
4243 (a_c_arg_t)num);
4244 checkedn--;
4248 else{ /* set All */
4249 for(num = 0; num < ab_count; num++){
4250 if(!entry_is_checked(pab->address_book->checks,
4251 (a_c_arg_t)num)){
4252 entry_set_checked(pab->address_book->checks,
4253 (a_c_arg_t)num);
4254 checkedn++;
4259 ps->mangled_body = 1;
4260 start_disp = 0;
4263 break;
4266 /*---------- Turn on ListMode -----------*/
4267 case MC_LISTMODE:
4268 as.checkboxes = 1;
4269 for(i = 0; i < as.n_addrbk; i++){
4270 pab = &as.adrbks[i];
4271 init_disp_form(pab, ps->VAR_ABOOK_FORMATS, i);
4274 (void)calculate_field_widths();
4275 ps->mangled_footer = 1;
4276 ps->mangled_body = 1;
4277 start_disp = 0;
4278 q_status_message(SM_ORDER, 0, 4,
4279 _("Use \"X\" to select addresses or lists"));
4280 break;
4283 /*--------- Compose -----------*/
4284 case MC_COMPOSE:
4285 (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 0);
4286 break;
4289 /*--------- Alt Compose -----------*/
4290 case MC_ROLE:
4291 (void)ab_compose_to_addr(as.top_ent+as.cur_row, 0, 1);
4292 break;
4295 #ifdef ENABLE_LDAP
4296 /*------ Query Directory ------*/
4297 case MC_QUERY_SERV:
4298 q_server:
4299 {char *err_mess = NULL, **err_mess_p;
4300 int query_type;
4302 if(!directory_ok){
4303 q_status_message(SM_ORDER, 0, 4,
4304 (style == SelectAddrLccCom)
4305 ? "Can't search server for Lcc"
4306 : "Can't search server from here");
4307 break;
4309 else if(as.checkboxes){
4310 q_status_message(SM_ORDER, 0, 4,
4311 "Can't search server when using ListMode");
4312 break;
4315 if(error_message)
4316 err_mess_p = error_message;
4317 else
4318 err_mess_p = &err_mess;
4321 * The query lines are indexed by their adrbk_num. (Just using
4322 * that because it was handy and not used for addrbooks.)
4324 query_type = adrbk_num_from_lineno(as.top_ent+as.cur_row);
4326 if((addr = query_server(ps, are_selecting, &quit, query_type,
4327 err_mess_p)) != NULL){
4328 if(are_selecting)
4329 return(addr);
4331 fs_give((void **)&addr);
4334 if(err_mess_p && *err_mess_p && !error_message){
4335 q_status_message(SM_ORDER, 0, 4, *err_mess_p);
4336 fs_give((void **)err_mess_p);
4340 break;
4341 #endif /* ENABLE_LDAP */
4344 /*----------- Where is (search) ----------------*/
4345 case MC_WHEREIS:
4346 warped = 0;
4347 new_top_ent = ab_whereis(&warped, command_line);
4349 if(new_top_ent != NO_LINE){
4350 if(warped || new_top_ent != as.top_ent){
4351 as.top_ent = new_top_ent;
4352 start_disp = 0;
4353 ps->mangled_body = 1;
4355 else
4356 current_changed_flag++;
4359 ps->mangled_footer = 1;
4360 break;
4363 /*----- Select entries to work on --*/
4364 case MC_SELECT:
4365 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4366 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4367 break;
4370 if(!cur_is_open()){
4371 if(entry_is_askserver(as.top_ent+as.cur_row))
4372 q_status_message(SM_ORDER, 0, 4,
4373 _("Select is only available from within an expanded address book"));
4374 else
4375 clickable_warning(as.top_ent+as.cur_row);
4377 break;
4380 dl = dlist(as.top_ent+as.cur_row);
4381 if(dl->type == Empty){
4382 empty_warning(as.top_ent+as.cur_row);
4383 break;
4386 {int were_selections = as.selections;
4388 ab_select(ps, pab ? pab->address_book : NULL,
4389 as.top_ent+as.cur_row, command_line, &start_disp);
4391 if((!were_selections && as.selections)
4392 || (were_selections && !as.selections)){
4393 ps->mangled_footer = 1;
4394 for(i = 0; i < as.n_addrbk; i++)
4395 init_disp_form(&as.adrbks[i],
4396 ps->VAR_ABOOK_FORMATS, i);
4400 break;
4403 /*----------- Select current entry ----------*/
4404 case MC_SELCUR:
4405 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4406 q_status_message(SM_ORDER, 0, 4, _("No entries to select"));
4407 break;
4410 if(entry_is_clickable(as.top_ent+as.cur_row)){
4411 clickable_warning(as.top_ent+as.cur_row);
4412 break;
4415 if(empty){
4416 empty_warning(as.top_ent+as.cur_row);
4417 break;
4420 if(is_addr(as.top_ent+as.cur_row)){
4421 dl = dlist(as.top_ent+as.cur_row);
4423 if(dl->type == ListHead || dl->type == Simple){
4424 int do_init_disp = 0;
4425 long ll;
4427 current_changed_flag++;
4429 if(entry_is_selected(pab->address_book->selects,
4430 (a_c_arg_t)dl->elnum)){
4431 DL_CACHE_S *dlc, dlc_restart;
4433 as.selections--;
4434 if(as.selections == 0)
4435 do_init_disp++;
4437 entry_unset_selected(pab->address_book->selects,
4438 (a_c_arg_t)dl->elnum);
4440 if(as.zoomed){
4441 dlc = get_dlc(as.top_ent+as.cur_row);
4442 if(as.selections){
4443 flush_dlc_from_cache(dlc);
4444 dlc = get_dlc(as.top_ent+as.cur_row);
4445 ps->mangled_body = 1;
4446 if(dlc->type == DlcEnd){
4447 r = prev_selectable_line(as.cur_row +
4448 as.top_ent,
4449 &new_line);
4450 if(r){
4451 as.cur_row = new_line - as.top_ent;
4452 if(as.cur_row < 0){
4453 as.top_ent += as.cur_row;
4454 as.cur_row = 0;
4455 start_disp = 0;
4457 else
4458 start_disp = as.cur_row;
4461 else
4462 start_disp = MAX(as.cur_row-1,0);
4464 else{
4465 dlc_restart = *dlc;
4466 as.zoomed = 0;
4467 q_status_message(SM_ORDER, 0, 2,
4468 _("Zoom Mode is now off, no entries selected"));
4470 warp_to_dlc(&dlc_restart, 0L);
4471 /* put current entry in middle of screen */
4472 as.top_ent =
4473 first_line(0L - (long)as.l_p_page/2L);
4474 as.cur_row = 0L - as.top_ent;
4475 ps->mangled_body = 1;
4476 start_disp = 0;
4479 else if(F_OFF(F_UNSELECT_WONT_ADVANCE,ps_global)){
4481 r = next_selectable_line(as.cur_row+as.top_ent,
4482 &new_line);
4483 if(r){
4485 for(ll = new_line;
4486 (dl=dlist(ll))->type != End;
4487 ll++)
4488 if(dl->type == ListHead || dl->type == Simple)
4489 break;
4491 if(dl->type != End)
4492 new_line = ll;
4494 as.cur_row = new_line - as.top_ent;
4495 if(as.cur_row >= as.l_p_page){
4496 /*-- Changed pages --*/
4497 as.top_ent += as.l_p_page;
4498 as.cur_row -= as.l_p_page;
4499 /* if it is still off screen */
4500 if(as.cur_row >= as.l_p_page){
4501 as.top_ent += (as.cur_row-as.l_p_page+1);
4502 as.cur_row = (as.l_p_page - 1);
4505 start_disp = 0;
4506 ps->mangled_body = 1;
4511 else{
4512 if(as.selections == 0)
4513 do_init_disp++;
4515 as.selections++;
4517 entry_set_selected(pab->address_book->selects,
4518 (a_c_arg_t)dl->elnum);
4519 r = next_selectable_line(as.cur_row+as.top_ent,
4520 &new_line);
4521 if(r){
4523 for(ll = new_line;
4524 (dl=dlist(ll))->type != End;
4525 ll++)
4526 if(dl->type == ListHead || dl->type == Simple)
4527 break;
4529 if(dl->type != End)
4530 new_line = ll;
4532 as.cur_row = new_line - as.top_ent;
4533 if(as.cur_row >= as.l_p_page){
4534 /*-- Changed pages --*/
4535 as.top_ent += as.l_p_page;
4536 as.cur_row -= as.l_p_page;
4537 /* if it is still off screen */
4538 if(as.cur_row >= as.l_p_page){
4539 as.top_ent += (as.cur_row-as.l_p_page+1);
4540 as.cur_row = (as.l_p_page - 1);
4543 start_disp = 0;
4544 ps->mangled_body = 1;
4550 * If we switch from selected to non-selected or
4551 * vice versa, we have to init_disp_form() for all
4552 * the addrbooks, in case we're using the X instead
4553 * of bold.
4555 if(do_init_disp){
4556 ps->mangled_footer = 1;
4557 for(i = 0; i < as.n_addrbk; i++)
4558 init_disp_form(&as.adrbks[i],
4559 ps->VAR_ABOOK_FORMATS, i);
4562 else
4563 q_status_message(SM_ORDER, 0, 4,
4564 _("You may not select list members, only whole lists or personal entries"));
4566 else
4567 q_status_message(SM_ORDER, 0, 4,
4568 _("You may only select addresses or lists"));
4570 break;
4573 /*--- Zoom in and look only at selected entries (or zoom out) --*/
4574 case MC_ZOOM:
4575 as.zoomed = (1 - as.zoomed);
4576 if(as.zoomed)
4577 ab_zoom((pab && pab->address_book) ? pab->address_book->selects
4578 : NULL,
4579 &start_disp);
4580 else{
4581 q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now off"));
4582 ab_unzoom(&start_disp);
4585 break;
4588 /*--- Apply a command -----------*/
4589 case MC_APPLY:
4590 if(as.selections){
4591 if(((ab_apply_cmd(ps, pab ? pab->address_book : NULL,
4592 as.top_ent+as.cur_row, command_line) &&
4593 F_ON(F_AUTO_UNZOOM, ps)) || !as.selections) && as.zoomed){
4595 ab_unzoom(NULL);
4596 ps_global->mangled_body = 1;
4600 * In case the line we're now at is not a selectable
4601 * field.
4603 * We set start_disp to zero here but rely on the called
4604 * routine to set mangled_body if appropriate.
4606 start_disp = 0;
4607 new_line = first_selectable_line(as.cur_row+as.top_ent);
4608 if(new_line != NO_LINE
4609 && new_line != as.cur_row+as.top_ent){
4610 as.cur_row = new_line - as.top_ent;
4611 if(as.cur_row < 0){
4612 as.top_ent += as.cur_row;
4613 as.cur_row = 0;
4615 else if(as.cur_row >= as.l_p_page){
4616 as.top_ent += (as.cur_row - as.l_p_page + 1);
4617 as.cur_row = as.l_p_page - 1;
4621 else
4622 q_status_message(SM_ORDER, 0, 2,
4623 _("No selected entries to apply command to"));
4625 break;
4628 /*--------- QUIT pine -----------*/
4629 case MC_QUIT:
4630 dprint((7, "Quitting pine from addrbook\n"));
4631 ps->next_screen = quit_screen;
4632 break;
4635 /*--------- Top of Folder list -----------*/
4636 case MC_COLLECTIONS:
4637 dprint((7, "Goto folder lister from addrbook\n"));
4638 ps->next_screen = folder_screen;
4639 break;
4642 /*---------- Open specific new folder ----------*/
4643 case MC_GOTO:
4644 dprint((7, "Goto from addrbook\n"));
4645 ab_goto_folder(command_line);
4646 break;
4649 /*--------- Index -----------*/
4650 case MC_INDEX:
4651 dprint((7, "Goto message index from addrbook\n"));
4652 if(THREADING()
4653 && sp_viewing_a_thread(ps->mail_stream)
4654 && unview_thread(ps, ps->mail_stream, ps->msgmap)){
4655 ps->next_screen = mail_index_screen;
4656 ps->view_skipped_index = 0;
4657 ps->mangled_screen = 1;
4660 ps->next_screen = mail_index_screen;
4661 break;
4664 /*----------------- Print --------------------*/
4665 case MC_PRINTTXT:
4666 (void)ab_print(0);
4667 ps->mangled_screen = 1;
4668 break;
4671 /*------ Copy entries into an abook ----*/
4672 case MC_SAVE:
4673 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4674 q_status_message(SM_ORDER, 0, 4, _("No entries to save"));
4675 break;
4678 if(entry_is_clickable(as.top_ent+as.cur_row)){
4679 clickable_warning(as.top_ent+as.cur_row);
4680 break;
4683 if(empty){
4684 empty_warning(as.top_ent+as.cur_row);
4685 break;
4688 (void)ab_save(ps, pab ? pab->address_book : NULL,
4689 as.top_ent+as.cur_row, command_line, 0);
4690 break;
4693 /*------ Forward an entry in mail -----------*/
4694 case MC_FORWARD:
4695 if(!any_addrs_avail(as.top_ent+as.cur_row)){
4696 q_status_message(SM_ORDER, 0, 4, _("No entries to forward"));
4697 break;
4700 if(entry_is_clickable(as.top_ent+as.cur_row)){
4701 clickable_warning(as.top_ent+as.cur_row);
4702 break;
4705 if(empty){
4706 empty_warning(as.top_ent+as.cur_row);
4707 break;
4710 if(!is_addr(as.top_ent+as.cur_row)){
4711 q_status_message(SM_ORDER, 0, 4, _("Nothing to forward"));
4712 break;
4715 dl = dlist(as.top_ent+as.cur_row);
4716 if(dl->type != ListHead && dl->type != Simple){
4717 q_status_message(SM_ORDER, 0, 4,
4718 _("Can only forward whole entries"));
4719 break;
4722 (void)ab_forward(ps, as.top_ent+as.cur_row, 0);
4723 ps->mangled_footer = 1;
4724 break;
4727 case MC_REPAINT:
4728 /* ^L attempts to resynchronize with changed addrbooks */
4729 if(adrbk_check_all_validity_now())
4730 (void)resync_screen(pab, style, checkedn);
4732 /* fall through */
4734 case MC_RESIZE:
4735 mark_status_dirty();
4736 mark_titlebar_dirty();
4737 mark_keymenu_dirty();
4738 ClearBody();
4739 ps->mangled_screen = 1;
4740 if(c == KEY_RESIZE)
4741 ab_resize();
4743 break;
4746 case MC_UTF8:
4747 bogus_utf8_command(utf8str, F_ON(F_USE_FK, ps) ? "F1" : "?");
4748 break;
4751 /*------ Some back compatibility messages -----*/
4752 case MC_UNKNOWN:
4753 if(c == 'e' && !are_selecting){
4754 q_status_message(SM_ORDER | SM_DING, 0, 2,
4755 _("Command \"E\" not defined. Use \"View/Update\" to edit an entry"));
4756 break;
4758 else if(c == 's'
4759 && !(are_selecting || entry_is_clickable(as.top_ent+as.cur_row))){
4760 q_status_message(SM_ORDER | SM_DING, 0, 2,
4761 _("Command \"S\" not defined. Use \"AddNew\" to create a list"));
4762 break;
4764 else if(c == 'z' && !are_selecting){
4765 q_status_message(SM_ORDER | SM_DING, 0, 2,
4766 _("Command \"Z\" not defined. Use \"View/Update\" to add to a list"));
4767 break;
4769 /* else, fall through */
4771 default:
4772 bogus_command(c, F_ON(F_USE_FK, ps) ? "F1" : "?");
4773 break;
4776 if(ps->next_screen != SCREEN_FUN_NULL)
4777 quit++;
4780 erase_selections();
4781 return NULL;
4786 * Turn on zoom mode and zoom in if applicable.
4788 * Args selecteds -- tells which entries are selected in current abook
4789 * start_disp -- Passed in so we can set it back in the caller
4791 void
4792 ab_zoom(EXPANDED_S *selecteds, int *start_disp)
4794 AddrScrn_Disp *dl;
4795 DL_CACHE_S *dlc, dlc_restart;
4797 as.zoomed = 1;
4799 if(as.selections){
4800 q_status_message(SM_ORDER, 0, 2, _("Zoom Mode is now on"));
4801 if(cur_is_open()){
4802 dl = dlist(as.top_ent+as.cur_row);
4803 if((dl->type == ListHead ||
4804 dl->type == Simple ||
4805 dl->type == ListEmpty ||
4806 dl->type == ListClickHere ||
4807 dl->type == ListEnt) &&
4808 entry_is_selected(selecteds, (a_c_arg_t)dl->elnum)){
4809 dlc = get_dlc(as.top_ent+as.cur_row);
4810 dlc_restart = *dlc;
4811 warp_to_dlc(&dlc_restart, 0L);
4812 /* put current entry in middle of screen */
4813 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
4814 as.cur_row = 0L - as.top_ent;
4816 else{
4817 long new_ent;
4819 warp_to_top_of_abook(as.cur);
4820 as.top_ent = 0L;
4821 new_ent = first_selectable_line(0L);
4822 if(new_ent == NO_LINE)
4823 as.cur_row = 0L;
4824 else
4825 as.cur_row = new_ent;
4827 /* if it is off screen */
4828 if(as.cur_row >= as.l_p_page){
4829 as.top_ent += (as.cur_row - as.l_p_page + 1);
4830 as.cur_row = (as.l_p_page - 1);
4834 else{
4835 dlc = get_dlc(as.top_ent+as.cur_row);
4836 dlc_restart = *dlc;
4837 warp_to_dlc(&dlc_restart, 0L);
4838 /* put current entry in middle of screen */
4839 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
4840 as.cur_row = 0L - as.top_ent;
4843 ps_global->mangled_body = 1;
4844 *start_disp = 0;
4846 else{
4847 as.zoomed = 0;
4848 q_status_message(SM_ORDER, 0, 2, _("No selected entries to zoom on"));
4854 * Turn off zoom mode and zoom out if applicable.
4856 * Args start_disp -- Passed in so we can set it back in the caller
4858 void
4859 ab_unzoom(int *start_disp)
4861 DL_CACHE_S *dlc, dlc_restart;
4863 as.zoomed = 0;
4864 dlc = get_dlc(as.top_ent+as.cur_row);
4865 if(dlc->type == DlcZoomEmpty){
4866 long new_ent;
4868 warp_to_beginning();
4869 as.top_ent = 0L;
4870 new_ent = first_selectable_line(0L);
4871 if(new_ent == NO_LINE)
4872 as.cur_row = 0L;
4873 else
4874 as.cur_row = new_ent;
4876 /* if it is off screen */
4877 if(as.cur_row >= as.l_p_page){
4878 as.top_ent += (as.cur_row - as.l_p_page + 1);
4879 as.cur_row = (as.l_p_page - 1);
4882 else{
4883 dlc_restart = *dlc;
4884 warp_to_dlc(&dlc_restart, 0L);
4885 /* put current entry in middle of screen */
4886 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
4887 as.cur_row = 0L - as.top_ent;
4890 ps_global->mangled_body = 1;
4891 if(start_disp)
4892 *start_disp = 0;
4897 * Post an empty addrbook warning.
4899 * Args: cur_line -- The current line position (in global display list)
4900 * of cursor
4902 void
4903 empty_warning(long int cur_line)
4905 register AddrScrn_Disp *dl;
4907 dl = dlist(cur_line);
4908 if(dl->type == NoAbooks)
4909 q_status_message(SM_ORDER, 0, 4,
4910 _("No address books configured, use Setup"));
4911 else if(dl->type == Empty)
4912 q_status_message(SM_ORDER, 0, 4, _("Address Book is Empty"));
4913 else
4914 q_status_message(SM_ORDER, 0, 4, _("Distribution List is Empty"));
4919 * Tell user to click on this to expand.
4921 * Args: cur_line -- The current line position (in global display list)
4922 * of cursor
4924 void
4925 clickable_warning(long int cur_line)
4927 register AddrScrn_Disp *dl;
4929 dl = dlist(cur_line);
4930 if(dl->type == Title || dl->type == ClickHereCmb)
4931 q_status_message(SM_ORDER, 0, 4, _("Address Book not expanded, use \">\" to expand"));
4932 else
4933 q_status_message(SM_ORDER, 0, 4, _("Distribution List not expanded, use \">\" to expand"));
4938 * Post a no tabs warning.
4940 void
4941 no_tabs_warning(void)
4943 q_status_message(SM_ORDER, 0, 4, "Tabs not allowed in address book");
4948 * Prompt for command to apply to selected entries
4950 * Returns: 1 if the entries are successfully commanded.
4951 * 0 otherwise
4954 ab_apply_cmd(struct pine *ps, AdrBk *abook, long int cur_line, int command_line)
4956 int ret = 0;
4957 static ESCKEY_S opts[] = {
4958 {'c', 'c', "C", "ComposeTo"},
4959 {'d', 'd', "D", "Delete"},
4960 {'%', '%', "%", "Print"},
4961 {'f', 'f', "F", "Forward"},
4962 {'s', 's', "S", "Save"},
4963 {'#', '#', "#", "Role"},
4964 { 0, '%', "", ""},
4965 {-1, 0, NULL, NULL}};
4966 #define PHANTOM_PRINT 6
4968 dprint((7, "- ab_apply_cmd -\n"));
4971 opts[PHANTOM_PRINT].ch = (F_ON(F_ENABLE_PRYNT, ps_global)) ? 'y' : -1;
4973 switch(radio_buttons("APPLY command : ", command_line, opts, 'z', 'x',
4974 NO_HELP, RB_NORM)){
4975 case 'c':
4976 ret = ab_compose_to_addr(cur_line, 1, 0);
4977 break;
4979 case '#':
4980 ret = ab_compose_to_addr(cur_line, 1, 1);
4981 break;
4983 case 'd':
4984 ret = ab_agg_delete(ps, 1);
4985 break;
4987 case '%':
4988 ret = ab_print(1);
4989 break;
4991 case 'f':
4992 ret = ab_forward(ps, cur_line, 1);
4993 break;
4995 case 's':
4996 ret = ab_save(ps, abook, cur_line, command_line, 1);
4997 break;
4999 case 'x':
5000 cmd_cancelled("Apply command");
5001 break;
5003 case 'z':
5004 q_status_message(SM_INFO, 0, 2,
5005 "Cancelled, there is no default command");
5006 break;
5009 ps_global->mangled_footer = 1;
5011 return(ret);
5016 * Allow user to mark some entries "selected".
5018 void
5019 ab_select(struct pine *ps, AdrBk *abook, long int cur_line, int command_line, int *start_disp)
5021 static ESCKEY_S sel_opts1[] = {
5022 {'a', 'a', "A", "unselect All"},
5023 { 0 , 'c', "C", NULL},
5024 {'b', 'b', "B", "Broaden selctn"},
5025 {'n', 'n', "N", "Narrow selctn"},
5026 {'f', 'f', "F", "Flip selected"},
5027 {-1, 0, NULL, NULL}
5029 static char *sel_pmt1 = "ALTER selection : ";
5030 static ESCKEY_S sel_opts2[] = {
5031 {'a', 'a', "A", "select All"},
5032 {'c', 'c', "C", "select Cur"},
5033 {'t', 't', "T", "Text"},
5034 {'s', 's', "S", "Status"},
5035 {-1, 0, NULL, NULL}
5037 static char *sel_pmt2 = "SELECT criteria : ";
5038 ESCKEY_S *sel_opts;
5039 HelpType help = NO_HELP;
5040 adrbk_cntr_t num, ab_count;
5041 int q = 0, rv = 0, narrow = 0,
5042 do_flush = 0, do_warp = 0, prevsel,
5043 move_current = 0, do_beginning = 0;
5044 long new_ent;
5045 AddrScrn_Disp *dl;
5046 DL_CACHE_S *dlc, dlc_restart;
5048 dprint((5, "- ab_select -\n"));
5050 if(cur_is_open()){ /* select applies only to this addrbook */
5052 ps->mangled_footer = 1;
5053 prevsel = as.selections;
5054 dl = dlist(cur_line);
5055 sel_opts = sel_opts2;
5058 * If already some selected, ask how to alter that selection.
5060 if(as.selections){
5061 sel_opts += 2; /* don't offer all or current below */
5062 if(dl && (dl->type == ListHead || dl->type == Simple)){
5063 sel_opts1[1].label = entry_is_selected(abook->selects,
5064 (a_c_arg_t)dl->elnum)
5065 ? "unselect Cur"
5066 : "select Cur";
5067 sel_opts1[1].ch = 'c';
5069 else
5070 sel_opts1[1].ch = -2; /* don't offer this choice */
5072 switch(q = radio_buttons(sel_pmt1, command_line, sel_opts1,
5073 'a', 'x', help, RB_NORM)){
5074 case 'n': /* narrow selection */
5075 narrow++;
5076 case 'b': /* broaden selection */
5077 q = 0; /* but don't offer criteria prompt */
5078 break;
5080 case 'c': /* select or unselect current */
5081 case 'a': /* select or unselect all */
5082 case 'f': /* flip selections */
5083 case 'x': /* cancel */
5084 break;
5086 default:
5087 q_status_message(SM_ORDER | SM_DING, 3, 3,
5088 "Unsupported Select option");
5089 return;
5093 if(abook && dl &&
5094 (dl->type == ListHead || dl->type == Simple)){
5095 sel_opts1[1].label = entry_is_selected(abook->selects,
5096 (a_c_arg_t)dl->elnum)
5097 ? "unselect Cur"
5098 : "select Cur";
5099 sel_opts1[1].ch = 'c';
5101 else
5102 sel_opts1[1].ch = -2; /* don't offer this choice */
5104 if(!q)
5105 q = radio_buttons(sel_pmt2, command_line, sel_opts,
5106 'c', 'x', help, RB_NORM);
5108 *start_disp = 0;
5109 dlc = get_dlc(cur_line);
5110 dlc_restart = *dlc;
5111 ab_count = adrbk_count(abook);
5113 switch(q){
5114 case 'x': /* cancel */
5115 cmd_cancelled("Select command");
5116 break;
5118 case 'c': /* select/unselect current */
5119 ps->mangled_body = 1;
5120 if(entry_is_selected(abook->selects, (a_c_arg_t)dl->elnum)){
5121 entry_unset_selected(abook->selects, (a_c_arg_t)dl->elnum);
5122 as.selections--;
5124 if(as.selections == 0 && as.zoomed){
5125 as.zoomed = 0;
5126 q_status_message(SM_ORDER, 0, 2,
5127 "Zoom Mode is now off, no entries selected");
5128 do_warp++;
5130 else if(as.zoomed){
5131 move_current++;
5132 do_flush++;
5134 else
5135 do_flush++;
5137 else{
5138 entry_set_selected(abook->selects, (a_c_arg_t)dl->elnum);
5139 as.selections++;
5141 if(as.selections == 1 && !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
5142 as.zoomed = 1;
5143 as.top_ent = dlc_restart.global_row;
5144 as.cur_row = 0;
5145 do_warp++;
5147 else
5148 do_flush++;
5151 break;
5153 case 'a':
5154 ps->mangled_body = 1;
5155 if(any_selected(abook->selects)){ /* unselect all */
5156 for(num = 0; num < ab_count; num++){
5157 if(entry_is_selected(abook->selects, (a_c_arg_t)num)){
5158 as.selections--;
5159 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5163 if(as.selections == 0 && as.zoomed){
5164 as.zoomed = 0;
5165 q_status_message(SM_ORDER, 0, 2,
5166 "Zoom Mode is now off, all entries UNselected");
5167 do_warp++;
5169 else{
5170 char bb[100];
5172 snprintf(bb, sizeof(bb), "%s entries UNselected%s%s%s",
5173 comatose(prevsel-as.selections),
5174 as.selections ? ", still " : "",
5175 as.selections ? comatose(as.selections) : "",
5176 as.selections ? " selected in other addrbooks" : "");
5177 bb[sizeof(bb)-1] = '\0';
5178 q_status_message(SM_ORDER, 0, 2, bb);
5179 if(as.zoomed)
5180 do_beginning++;
5181 else
5182 do_flush++;
5185 else{ /* select all */
5186 for(num = 0; num < ab_count; num++){
5187 if(!entry_is_selected(abook->selects, (a_c_arg_t)num)){
5188 as.selections++;
5189 entry_set_selected(abook->selects, (a_c_arg_t)num);
5193 q_status_message1(SM_ORDER, 0, 2, "All %s entries selected",
5194 comatose(ab_count));
5195 if(prevsel == 0 && as.selections > 0 &&
5196 !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
5197 as.zoomed = 1;
5198 as.top_ent = dlc_restart.global_row - as.cur_row;
5199 do_warp++;
5201 else if(dlc_restart.type == DlcZoomEmpty &&
5202 as.selections > prevsel)
5203 do_beginning++;
5204 else if(as.zoomed)
5205 do_warp++;
5206 else
5207 do_flush++;
5210 break;
5212 case 'f': /* flip selections in this abook */
5213 ps->mangled_body = 1;
5214 for(num = 0; num < ab_count; num++){
5215 if(entry_is_selected(abook->selects, (a_c_arg_t)num)){
5216 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5217 as.selections--;
5219 else{
5220 entry_set_selected(abook->selects, (a_c_arg_t)num);
5221 as.selections++;
5225 if(as.zoomed){
5226 if(as.selections)
5227 do_beginning++;
5228 else{
5229 as.zoomed = 0;
5230 q_status_message(SM_ORDER, 0, 2, "Zoom Mode is now off");
5231 do_warp++;
5234 else
5235 do_warp++;
5237 q_status_message1(SM_ORDER, 0, 2, "%s entries now selected",
5238 comatose(as.selections));
5240 break;
5242 case 't':
5243 case 's':
5244 switch(q){
5245 case 't':
5246 rv = ab_select_text(abook, narrow);
5247 break;
5249 case 's':
5250 rv = ab_select_type(abook, narrow);
5251 break;
5254 if(!rv){
5255 ps->mangled_body = 1;
5256 if(dlc_restart.type == DlcZoomEmpty &&
5257 as.selections > prevsel)
5258 do_beginning++;
5259 else if(as.zoomed){
5260 if(as.selections == 0){
5261 as.zoomed = 0;
5262 q_status_message(SM_ORDER, 0, 2,
5263 "Zoom Mode is now off");
5264 do_warp++;
5266 else
5267 do_beginning++;
5269 else{
5270 if(prevsel == 0 && as.selections > 0 &&
5271 !as.zoomed && F_ON(F_AUTO_ZOOM, ps)){
5272 as.zoomed = 1;
5273 do_beginning++;
5275 else
5276 do_warp++;
5279 if(prevsel == as.selections && prevsel > 0){
5280 if(as.selections == 1)
5281 q_status_message(SM_ORDER, 0, 2,
5282 "No change resulted, 1 entry remains selected");
5283 else
5284 q_status_message1(SM_ORDER, 0, 2,
5285 "No change resulted, %s entries remain selected",
5286 comatose(as.selections));
5288 else if(prevsel == 0){
5289 if(as.selections == 1)
5290 q_status_message(SM_ORDER, 0, 2,
5291 "Select matched 1 entry");
5292 else if(as.selections > 1)
5293 q_status_message1(SM_ORDER, 0, 2,
5294 "Select matched %s entries",
5295 comatose(as.selections));
5296 else
5297 q_status_message(SM_ORDER, 0, 2,
5298 "Select failed! No entries selected");
5300 else if(as.selections == 0){
5301 if(prevsel == 1)
5302 q_status_message(SM_ORDER, 0, 2,
5303 "The single selected entry is UNselected");
5304 else
5305 q_status_message1(SM_ORDER, 0, 2,
5306 "All %s entries UNselected",
5307 comatose(prevsel));
5309 else if(narrow){
5310 if(as.selections == 1 && (prevsel-as.selections) == 1)
5311 q_status_message(SM_ORDER, 0, 2,
5312 "1 entry now selected, 1 entry was UNselected");
5313 else if(as.selections == 1)
5314 q_status_message1(SM_ORDER, 0, 2,
5315 "1 entry now selected, %s entries were UNselected",
5316 comatose(prevsel-as.selections));
5317 else if((prevsel-as.selections) == 1)
5318 q_status_message1(SM_ORDER, 0, 2,
5319 "%s entries now selected, 1 entry was UNselected",
5320 comatose(as.selections));
5321 else
5322 q_status_message2(SM_ORDER, 0, 2,
5323 "%s entries now selected, %s entries were UNselected",
5324 comatose(as.selections),
5325 comatose(prevsel-as.selections));
5327 else{
5328 if((as.selections-prevsel) == 1)
5329 q_status_message1(SM_ORDER, 0, 2,
5330 "1 new entry selected, %s entries now selected",
5331 comatose(as.selections));
5332 else if(as.selections == 1)
5333 q_status_message1(SM_ORDER, 0, 2,
5334 "%s new entries selected, 1 entry now selected",
5335 comatose(as.selections-prevsel));
5336 else
5337 q_status_message2(SM_ORDER, 0, 2,
5338 "%s new entries selected, %s entries now selected",
5339 comatose(as.selections-prevsel),
5340 comatose(as.selections));
5344 break;
5346 default :
5347 q_status_message(SM_ORDER | SM_DING, 3, 3,
5348 "Unsupported Select option");
5349 break;
5352 else{
5353 if(F_ON(F_CMBND_ABOOK_DISP,ps_global))
5354 q_status_message(SM_ORDER | SM_DING, 3, 3,
5355 "Select is only available from within an expanded address book");
5356 else
5357 q_status_message(SM_ORDER | SM_DING, 3, 3,
5358 "Select is only available when viewing an individual address book");
5360 return;
5363 if(rv)
5364 return;
5366 if(do_beginning){
5367 warp_to_beginning(); /* just go to top */
5368 as.top_ent = 0L;
5369 new_ent = first_selectable_line(0L);
5370 if(new_ent == NO_LINE)
5371 as.cur_row = 0L;
5372 else
5373 as.cur_row = new_ent;
5375 /* if it is off screen */
5376 if(as.cur_row >= as.l_p_page){
5377 as.top_ent += (as.cur_row - as.l_p_page + 1);
5378 as.cur_row = (as.l_p_page - 1);
5381 else if(do_flush)
5382 flush_dlc_from_cache(&dlc_restart);
5383 else if(do_warp)
5384 warp_to_dlc(&dlc_restart, dlc_restart.global_row);
5386 if(move_current){
5387 dlc = get_dlc(cur_line);
5388 if(dlc->type == DlcEnd){
5389 as.cur_row--;
5390 if(as.cur_row < 0){
5391 as.top_ent += as.cur_row; /* plus a negative number */
5392 as.cur_row = 0;
5393 *start_disp = 0;
5395 else
5396 *start_disp = as.cur_row;
5399 else
5400 *start_disp = as.cur_row;
5406 * Selects based on whether an entry is a list or not.
5408 * Returns: 0 if search went ok
5409 * -1 if there was a problem
5412 ab_select_type(AdrBk *abook, int narrow)
5414 static ESCKEY_S ab_sel_type_opt[] = {
5415 {'s', 's', "S", "Simple"},
5416 {'l', 'l', "L", "List"},
5417 {-1, 0, NULL, NULL}
5419 static char *ab_sel_type = "Select Lists or Simples (non Lists) ? ";
5420 int type;
5421 adrbk_cntr_t num, ab_count;
5423 dprint((6, "- ab_select_type -\n"));
5425 if(!abook)
5426 return -1;
5428 switch(type = radio_buttons(ab_sel_type, -FOOTER_ROWS(ps_global),
5429 ab_sel_type_opt, 'l', 'x', NO_HELP, RB_NORM)){
5430 case 'l':
5431 break;
5433 case 's':
5434 break;
5436 case 'x':
5437 cmd_cancelled("Select");
5438 return -1;
5440 default:
5441 dprint((1,"\n - BOTCH: ab_select_type unknown option\n"));
5442 return -1;
5445 ab_count = adrbk_count(abook);
5446 for(num = 0; num < ab_count; num++){
5447 AdrBk_Entry *abe;
5448 int matched;
5451 * If it won't possibly change state, don't look at it.
5453 if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) ||
5454 (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num)))
5455 continue;
5457 abe = adrbk_get_ae(abook, (a_c_arg_t) num);
5458 matched = ((type == 's' && abe->tag == Single) ||
5459 (type == 'l' && abe->tag == List));
5461 if(narrow && !matched){
5462 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5463 as.selections--;
5465 else if(!narrow && matched){
5466 entry_set_selected(abook->selects, (a_c_arg_t)num);
5467 as.selections++;
5471 return 0;
5476 * Selects based on string matches in various addrbook fields.
5478 * Returns: 0 if search went ok
5479 * -1 if there was a problem
5482 ab_select_text(AdrBk *abook, int narrow)
5484 static ESCKEY_S ab_sel_text_opt[] = {
5485 {'n', 'n', "N", "Nickname"},
5486 {'a', 'a', "A", "All Text"},
5487 {'f', 'f', "F", "Fullname"},
5488 {'e', 'e', "E", "Email Addrs"},
5489 {'c', 'c', "C", "Comment"},
5490 {'z', 'z', "Z", "Fcc"},
5491 {-1, 0, NULL, NULL}
5493 static char *ab_sel_text =
5494 "Select based on Nickname, All text, Fullname, Addrs, Comment, or Fcc ? ";
5495 HelpType help = NO_HELP;
5496 int type, r;
5497 char sstring[80+1], prompt[80];
5498 adrbk_cntr_t num, ab_count;
5499 char *fmt = "String in \"%s\" to match : ";
5501 dprint((6, "- ab_select_text -\n"));
5503 if(!abook)
5504 return -1;
5506 switch(type = radio_buttons(ab_sel_text, -FOOTER_ROWS(ps_global),
5507 ab_sel_text_opt, 'a', 'x', NO_HELP, RB_NORM)){
5508 case 'n':
5509 snprintf(prompt, sizeof(prompt), fmt, "Nickname");
5510 break;
5511 case 'a':
5512 snprintf(prompt, sizeof(prompt), fmt, "All Text");
5513 break;
5514 case 'f':
5515 snprintf(prompt, sizeof(prompt), fmt, "Fullname");
5516 break;
5517 case 'e':
5518 snprintf(prompt, sizeof(prompt), fmt, "addresses");
5519 break;
5520 case 'c':
5521 snprintf(prompt, sizeof(prompt), fmt, "Comment");
5522 break;
5523 case 'z':
5524 snprintf(prompt, sizeof(prompt), fmt, "Fcc");
5525 break;
5526 case 'x':
5527 break;
5528 default:
5529 dprint((1,"\n - BOTCH: ab_select_text unknown option\n"));
5530 return -1;
5533 prompt[sizeof(prompt)-1] = '\0';
5535 sstring[0] = '\0';
5536 while(type != 'x'){
5537 int flags = OE_APPEND_CURRENT;
5539 r = optionally_enter(sstring, -FOOTER_ROWS(ps_global), 0,
5540 sizeof(sstring), prompt, NULL, help, &flags);
5541 switch(r){
5542 case 3: /* BUG, no help */
5543 case 4:
5544 continue;
5546 default:
5547 break;
5550 if(r == 1 || sstring[0] == '\0')
5551 r = 'x';
5553 break;
5556 if(type == 'x' || r == 'x'){
5557 cmd_cancelled("Select");
5558 return -1;
5561 ab_count = adrbk_count(abook);
5562 for(num = 0; num < ab_count; num++){
5563 AdrBk_Entry *abe;
5564 int matched;
5567 * If it won't possibly change state, don't look at it.
5569 if((narrow && !entry_is_selected(abook->selects, (a_c_arg_t)num)) ||
5570 (!narrow && entry_is_selected(abook->selects, (a_c_arg_t)num)))
5571 continue;
5573 abe = adrbk_get_ae(abook, (a_c_arg_t) num);
5574 matched = match_check(abe, type, sstring);
5576 if(narrow && !matched){
5577 entry_unset_selected(abook->selects, (a_c_arg_t)num);
5578 as.selections--;
5580 else if(!narrow && matched){
5581 entry_set_selected(abook->selects, (a_c_arg_t)num);
5582 as.selections++;
5586 return 0;
5591 * Returns: 1 if a match is found for the entry
5592 * 0 no match
5593 * -1 error
5596 match_check(AdrBk_Entry *abe, int type, char *string)
5598 static int err = -1;
5599 static int match = 1;
5600 static int nomatch = 0;
5601 unsigned int checks;
5602 #define CK_NICKNAME 0x01
5603 #define CK_FULLNAME 0x02
5604 #define CK_ADDRESSES 0x04
5605 #define CK_FCC 0x08
5606 #define CK_COMMENT 0x10
5607 #define CK_ALL 0x1f
5609 checks = 0;
5611 switch(type){
5612 case 'n': /* Nickname */
5613 checks |= CK_NICKNAME;
5614 break;
5616 case 'f': /* Fullname */
5617 checks |= CK_FULLNAME;
5618 break;
5620 case 'e': /* Addrs */
5621 checks |= CK_ADDRESSES;
5622 break;
5624 case 'a': /* All Text */
5625 checks |= CK_ALL;
5626 break;
5628 case 'z': /* Fcc */
5629 checks |= CK_FCC;
5630 break;
5632 case 'c': /* Comment */
5633 checks |= CK_COMMENT;
5634 break;
5636 default:
5637 q_status_message(SM_ORDER | SM_DING, 3, 3, "Unknown type");
5638 return(err);
5641 if(checks & CK_NICKNAME){
5642 if(abe && abe->nickname && srchstr(abe->nickname, string))
5643 return(match);
5646 if(checks & CK_FULLNAME){
5647 if(abe &&
5648 abe->fullname &&
5649 abe->fullname[0] &&
5650 srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5651 SIZEOF_20KBUF, abe->fullname),
5652 string))
5653 return(match);
5656 if(checks & CK_ADDRESSES){
5657 if(abe &&
5658 abe->tag == Single &&
5659 abe->addr.addr &&
5660 abe->addr.addr[0] &&
5661 srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5662 SIZEOF_20KBUF, abe->addr.addr),
5663 string))
5664 return(match);
5665 else if(abe &&
5666 abe->tag == List &&
5667 abe->addr.list){
5668 char **p;
5670 for(p = abe->addr.list; p != NULL && *p != NULL; p++){
5671 if(srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5672 SIZEOF_20KBUF, *p),
5673 string))
5674 return(match);
5679 if(checks & CK_FCC){
5680 if(abe &&
5681 abe->fcc &&
5682 abe->fcc[0] &&
5683 srchstr((char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
5684 SIZEOF_20KBUF, abe->fcc),
5685 string))
5686 return(match);
5689 if(checks & CK_COMMENT && abe && abe->extra && abe->extra[0]){
5690 size_t n, len;
5691 unsigned char *p, *tmp = NULL;
5692 int found_it = 0;
5694 if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){
5695 len = n+1;
5696 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
5698 else{
5699 len = SIZEOF_20KBUF;
5700 p = (unsigned char *)tmp_20k_buf;
5703 if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra), string))
5704 found_it++;
5706 if(tmp)
5707 fs_give((void **)&tmp);
5709 if(found_it)
5710 return(match);
5713 return(nomatch);
5718 * Go to folder.
5720 * command_line -- The screen line on which to prompt
5722 void
5723 ab_goto_folder(int command_line)
5725 char *go_folder;
5726 CONTEXT_S *tc;
5727 int notrealinbox = 0;
5729 dprint((2, "- ab_goto_folder -\n"));
5731 tc = ps_global->context_current;
5733 go_folder = broach_folder(command_line, 1, &notrealinbox, &tc);
5735 if(go_folder != NULL)
5736 visit_folder(ps_global, go_folder, tc, NULL, notrealinbox ? 0L : DB_INBOXWOCNTXT);
5741 * Execute whereis command.
5743 * Returns value of the new top entry, or NO_LINE if cancelled.
5745 long
5746 ab_whereis(int *warped, int command_line)
5748 int rc, wrapped = 0;
5749 long new_top_ent, new_line;
5751 dprint((5, "- ab_whereis -\n"));
5753 rc = search_book(as.top_ent+as.cur_row, command_line,
5754 &new_line, &wrapped, warped);
5756 new_top_ent = NO_LINE;
5758 if(rc == -2)
5759 q_status_message(SM_INFO, 0, 2, _("Address book search cancelled"));
5761 else if(rc == -1)
5762 q_status_message(SM_ORDER, 0, 4, _("Word not found"));
5764 else if(rc == 0){ /* search succeeded */
5766 if(wrapped == 1)
5767 q_status_message(SM_INFO, 0, 2, _("Search wrapped to beginning"));
5768 else if(wrapped == 2)
5769 q_status_message(SM_INFO, 0, 2,
5770 _("Current line contains the only match"));
5772 /* know match is on the same page */
5773 if(!*warped &&
5774 new_line >= as.top_ent &&
5775 new_line < as.top_ent+as.l_p_page)
5776 new_top_ent = as.top_ent;
5777 /* don't know whether it is or not, reset top_ent */
5778 else
5779 new_top_ent = first_line(new_line - as.l_p_page/2);
5781 as.cur_row = new_line - new_top_ent;
5784 return(new_top_ent);
5789 * recalculate display parameters for window size change
5791 void
5792 ab_resize(void)
5794 long new_line;
5795 int old_l_p_p;
5796 DL_CACHE_S dlc_buf, *dlc_restart;
5798 old_l_p_p = as.l_p_page;
5799 as.l_p_page = ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)
5800 - HEADER_ROWS(ps_global);
5802 dprint((9, "- ab_resize -\n l_p_p was %d, now %d\n",
5803 old_l_p_p, as.l_p_page));
5805 if(as.l_p_page <= 0){
5806 as.no_op_possbl++;
5807 return;
5809 else
5810 as.no_op_possbl = 0;
5812 new_line = as.top_ent + as.cur_row;
5813 as.top_ent = first_line(new_line - as.l_p_page/2);
5814 as.cur_row = new_line - as.top_ent;
5815 as.old_cur_row = as.cur_row;
5817 /* need this to re-initialize Text and Title lines in display */
5818 /* get the old current line (which may be the wrong width) */
5819 dlc_restart = get_dlc(new_line);
5820 /* flush it from cache */
5821 flush_dlc_from_cache(dlc_restart);
5822 /* re-get it (should be right now) */
5823 dlc_restart = get_dlc(new_line);
5824 /* copy it to local storage */
5825 dlc_buf = *dlc_restart;
5826 dlc_restart = &dlc_buf;
5827 /* flush everything from cache and add that one line back in */
5828 warp_to_dlc(dlc_restart, new_line);
5833 * Returns 0 if we know for sure that there are no
5834 * addresses available in any of the addressbooks.
5836 * Easiest would be to start at 0 and go through the addrbook, but that will
5837 * be very slow for big addrbooks if we're not close to 0 already. Instead,
5838 * starting_hint is a hint at a good place to start looking.
5841 any_addrs_avail(long int starting_hint)
5843 register AddrScrn_Disp *dl;
5844 long lineno;
5847 * Look from lineno backwards first, in hopes of finding it in cache.
5849 lineno = starting_hint;
5850 for(dl=dlist(lineno);
5851 dl->type != Beginning;
5852 dl = dlist(--lineno)){
5853 if(dl->type == NoAbooks)
5854 return 0;
5856 switch(dl->type){
5857 case Simple:
5858 case ListEnt:
5859 case ListHead:
5860 case ZoomEmpty:
5861 case Title:
5862 case ListClickHere:
5863 case ClickHereCmb:
5864 return 1;
5865 default:
5866 break;
5870 /* search from here forward if we still don't know */
5871 lineno = starting_hint;
5872 for(dl=dlist(lineno);
5873 dl->type != End;
5874 dl = dlist(++lineno)){
5875 if(dl->type == NoAbooks)
5876 return 0;
5878 switch(dl->type){
5879 case Simple:
5880 case ListEnt:
5881 case ListHead:
5882 case ZoomEmpty:
5883 case Title:
5884 case ListClickHere:
5885 case ClickHereCmb:
5886 return 1;
5887 default:
5888 break;
5892 return 0;
5897 * Returns 1 if this line is a clickable line.
5900 entry_is_clickable(long int lineno)
5902 register AddrScrn_Disp *dl;
5904 if((dl = dlist(lineno)) &&
5905 (dl->type == Title || dl->type == ListClickHere ||
5906 dl->type == ClickHereCmb))
5907 return 1;
5909 return 0;
5914 * Returns 1 if this line is a clickable Title line.
5917 entry_is_clickable_title(long int lineno)
5919 register AddrScrn_Disp *dl;
5921 if((dl = dlist(lineno)) && (dl->type == Title || dl->type == ClickHereCmb))
5922 return 1;
5924 return 0;
5929 * Returns 1 if an address or list is selected.
5932 is_addr(long int lineno)
5934 register AddrScrn_Disp *dl;
5936 if((dl = dlist(lineno)) && (dl->type == ListHead ||
5937 dl->type == ListEnt ||
5938 dl->type == Simple))
5939 return 1;
5941 return 0;
5946 * Returns 1 if type of line is Empty.
5949 is_empty(long int lineno)
5951 register AddrScrn_Disp *dl;
5953 if((dl = dlist(lineno)) &&
5954 (dl->type == Empty || dl->type == ListEmpty || dl->type == ZoomEmpty))
5955 return 1;
5957 return 0;
5962 * Returns 1 if lineno is a list entry
5965 entry_is_listent(long int lineno)
5967 register AddrScrn_Disp *dl;
5969 if((dl = dlist(lineno)) && dl->type == ListEnt)
5970 return 1;
5972 return 0;
5977 * Returns 1 if lineno is a fake addrbook for config screen add
5978 * or if it is an AskServer line for LDAP query
5981 entry_is_addkey(long int lineno)
5983 register AddrScrn_Disp *dl;
5985 if((dl = dlist(lineno)) && (dl->type == AddFirstPers ||
5986 dl->type == AddFirstGlob ||
5987 dl->type == AskServer))
5988 return 1;
5990 return 0;
5995 * Returns 1 if lineno is the line to ask for directory server query
5998 entry_is_askserver(long int lineno)
6000 register AddrScrn_Disp *dl;
6002 if((dl = dlist(lineno)) && dl->type == AskServer)
6003 return 1;
6005 return 0;
6010 * Returns 1 if an add abook here would be global, not personal
6013 add_is_global(long int lineno)
6015 register AddrScrn_Disp *dl;
6017 dl = dlist(lineno);
6019 if(dl){
6020 if(dl->type == Title){
6021 register PerAddrBook *pab;
6023 pab = &as.adrbks[as.cur];
6024 if(pab && pab->type & GLOBAL)
6025 return 1;
6027 else if(dl->type == AddFirstGlob)
6028 return 1;
6031 return 0;
6036 * Find the first line greater than or equal to line. (Any line, not
6037 * necessarily selectable.)
6039 * Returns the line number of the found line or NO_LINE if there is none.
6041 * Warning: This just starts at the passed in line and goes forward until
6042 * it runs into a line that isn't a Beginning line. If the line passed in
6043 * is not in the dlc cache, it will have no way to know when it gets to the
6044 * real beginning.
6046 long
6047 first_line(long int line)
6049 long lineno;
6050 register PerAddrBook *pab;
6051 int i;
6053 for(lineno=line;
6054 dlist(lineno)->type == Beginning;
6055 lineno++)
6056 ;/* do nothing */
6058 if(dlist(lineno)->type != End)
6059 return(lineno);
6060 else{
6061 for(i = 0; i < as.n_addrbk; i++){
6062 pab = &as.adrbks[i];
6063 if(pab->ostatus != Open &&
6064 pab->ostatus != HalfOpen &&
6065 pab->ostatus != ThreeQuartOpen)
6066 return NO_LINE;
6069 as.no_op_possbl++;
6070 return(NO_LINE);
6076 * Find the line number of the next selectable line.
6078 * Args: cur_line -- The current line position (in global display list)
6079 * of cursor
6080 * new_line -- Return value: new line position
6082 * Result: The new line number is set.
6083 * The value 1 is returned if OK or 0 if there is no next line.
6086 next_selectable_line(long int cur_line, long int *new_line)
6088 /* skip over non-selectable lines */
6089 for(cur_line++;
6090 !line_is_selectable(cur_line) && dlist(cur_line)->type != End;
6091 cur_line++)
6092 ;/* do nothing */
6094 if(dlist(cur_line)->type == End)
6095 return 0;
6097 *new_line = cur_line;
6098 return 1;
6103 * Find the line number of the previous selectable line.
6105 * Args: cur_line -- The current line position (in global display list)
6106 * of cursor
6107 * new_line -- Return value: new line position
6109 * Result: The new line number is set.
6110 * The value 1 is returned if OK or 0 if there is no previous line.
6113 prev_selectable_line(long int cur_line, long int *new_line)
6115 /* skip backwards over non-selectable lines */
6116 for(cur_line--;
6117 !line_is_selectable(cur_line) && dlist(cur_line)->type != Beginning;
6118 cur_line--)
6119 ;/* do nothing */
6121 if(dlist(cur_line)->type == Beginning)
6122 return 0;
6124 *new_line = cur_line;
6126 return 1;
6131 * Resync the display with the addrbooks, which were just discovered
6132 * to be out of sync with the display.
6134 * Returns 1 -- current address book had to be resynced
6135 * 0 -- current address book not resynced
6138 resync_screen(PerAddrBook *pab, AddrBookArg style, int checkedn)
6140 AddrScrn_Disp *dl;
6141 int current_resynced = 0;
6142 DL_CACHE_S dlc_restart, *dlc = NULL;
6145 * The test below gives conditions under which it is safe to go ahead
6146 * and resync all the addrbooks that are out of sync now, and the
6147 * display won't change. Otherwise, we have to be careful to preserve
6148 * some of our state so that we can attempt to restore the screen to
6149 * a state that is as close as possible to what we have now. If the
6150 * currently opened address book (pab) is out of date we will lose
6151 * the expanded state of its distribution lists, which is no big deal.
6152 * Since resyncing also loses the checked status if we're selecting with
6153 * ListMode, we don't even attempt it in that case.
6155 if((ab_nesting_level < 2 && !cur_is_open() && checkedn == 0) ||
6156 (style == AddrBookScreen &&
6157 pab &&
6158 pab->address_book &&
6159 !(pab->address_book->flags & FILE_OUTOFDATE ||
6160 (pab->address_book->rd &&
6161 pab->address_book->rd->flags & REM_OUTOFDATE)))){
6163 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
6164 dlc = get_dlc(as.top_ent+as.cur_row);
6165 dlc_restart = *dlc;
6168 if(adrbk_check_and_fix_all(1, 0, 1)){
6169 ps_global->mangled_footer = 1; /* why? */
6170 if(F_ON(F_CMBND_ABOOK_DISP,ps_global)){
6171 warp_to_dlc(&dlc_restart, 0L);
6172 /* put current entry in middle of screen */
6173 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
6174 as.cur_row = 0L - as.top_ent;
6175 ps_global->mangled_screen = 1;
6179 else if(style == AddrBookScreen){
6180 char *savenick = NULL;
6181 AdrBk_Entry *abe;
6182 adrbk_cntr_t old_entry_num, new_entry_num;
6183 long old_global_row;
6185 current_resynced++;
6188 * We're going to try to get the nickname of the current
6189 * entry and find it again after the resync.
6191 dl = dlist(as.top_ent+as.cur_row);
6192 if(dl->type == ListEnt ||
6193 dl->type == ListEmpty ||
6194 dl->type == ListClickHere ||
6195 dl->type == ListHead ||
6196 dl->type == Simple){
6197 abe = ae(as.top_ent+as.cur_row);
6198 old_entry_num = dl->elnum;
6199 old_global_row = as.top_ent+as.cur_row;
6200 if(abe && abe->nickname && abe->nickname[0])
6201 savenick = cpystr(abe->nickname);
6204 /* this will close and re-open current addrbook */
6205 (void)adrbk_check_and_fix_all(1, 0, 1);
6207 abe = NULL;
6208 if(savenick){
6209 abe = adrbk_lookup_by_nick(pab->address_book, savenick,
6210 &new_entry_num);
6211 fs_give((void **)&savenick);
6214 if(abe){ /* If we found the same nickname, move to it */
6215 dlc_restart.adrbk_num = as.cur;
6216 dlc_restart.dlcelnum = new_entry_num;
6217 dlc_restart.type = (abe->tag == Single)
6218 ? DlcSimple : DlcListHead;
6219 if(old_entry_num == new_entry_num)
6220 warp_to_dlc(&dlc_restart, old_global_row);
6221 else
6222 warp_to_dlc(&dlc_restart, 0L);
6224 else
6225 warp_to_top_of_abook(as.cur);
6227 if(!abe || old_entry_num != new_entry_num){
6228 /* put current entry in middle of screen */
6229 as.top_ent = first_line(0L - (long)as.l_p_page/2L);
6230 as.cur_row = 0L - as.top_ent;
6234 return(current_resynced);
6239 * Erase all the check marks.
6241 void
6242 erase_checks(void)
6244 int i;
6245 PerAddrBook *pab;
6247 for(i = 0; i < as.n_addrbk; i++){
6248 pab = &as.adrbks[i];
6249 if(pab->address_book && pab->address_book->checks)
6250 exp_free(pab->address_book->checks);
6252 init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
6258 * Erase all the selections.
6260 void
6261 erase_selections(void)
6263 int i;
6264 PerAddrBook *pab;
6266 for(i = 0; i < as.n_addrbk; i++){
6267 pab = &as.adrbks[i];
6268 if(pab->address_book && pab->address_book->selects)
6269 exp_free(pab->address_book->selects);
6272 for(i = 0; i < as.n_addrbk; i++){
6273 pab = &as.adrbks[i];
6274 init_disp_form(pab, ps_global->VAR_ABOOK_FORMATS, i);
6277 as.selections = 0;
6282 * return values of search_in_one_line are or'd combination of these
6284 #define MATCH_NICK 0x1 /* match in field 0 */
6285 #define MATCH_FULL 0x2 /* match in field 1 */
6286 #define MATCH_ADDR 0x4 /* match in field 2 */
6287 #define MATCH_FCC 0x8 /* match in fcc field */
6288 #define MATCH_COMMENT 0x10 /* match in comment field */
6289 #define MATCH_BIGFIELD 0x20 /* match in one of the fields that crosses the
6290 whole screen, like a Title field */
6291 #define MATCH_LISTMEM 0x40 /* match list member */
6293 * Prompt user for search string and call find_in_book.
6295 * Args: cur_line -- The current line position (in global display list)
6296 * of cursor
6297 * command_line -- The screen line to prompt on
6298 * new_line -- Return value: new line position
6299 * wrapped -- Wrapped to beginning of display, tell user
6300 * warped -- Warped to a new location in the addrbook
6302 * Result: The new line number is set if the search is successful.
6303 * Returns 0 if found, -1 if not, -2 if cancelled.
6307 search_book(long int cur_line, int command_line, long int *new_line, int *wrapped, int *warped)
6309 int i=0, find_result, rc, flags, ku;
6310 static HISTORY_S *history = NULL;
6311 char search_string[MAX_SEARCH + 1];
6312 char prompt[MAX_SEARCH + 50], nsearch_string[MAX_SEARCH+1], *p;
6313 HelpType help;
6314 ESCKEY_S ekey[6];
6315 PerAddrBook *pab;
6316 long nl;
6318 dprint((7, "- search_book -\n"));
6320 init_hist(&history, HISTSIZE);
6322 search_string[0] = '\0';
6323 if((p = get_prev_hist(history, "", 0, NULL)) != NULL){
6324 strncpy(search_string, p, sizeof(search_string));
6325 search_string[sizeof(search_string)-1] = '\0';
6328 snprintf(prompt, sizeof(prompt), _("Word to search for [%.*s]: "), MAX_SEARCH, search_string);
6329 prompt[sizeof(prompt)-1] = '\0';
6330 help = NO_HELP;
6331 nsearch_string[0] = '\0';
6333 ekey[i].ch = 0;
6334 ekey[i].rval = 0;
6335 ekey[i].name = "";
6336 ekey[i++].label = "";
6338 ekey[i].ch = ctrl('Y');
6339 ekey[i].rval = 10;
6340 ekey[i].name = "^Y";
6341 /* TRANSLATORS: User is searching in address book. One of the options is to
6342 search for the First Address */
6343 ekey[i++].label = _("First Adr");
6345 ekey[i].ch = ctrl('V');
6346 ekey[i].rval = 11;
6347 ekey[i].name = "^V";
6348 /* TRANSLATORS: Last Address */
6349 ekey[i++].label = _("Last Adr");
6351 ekey[i].ch = KEY_UP;
6352 ekey[i].rval = 30;
6353 ekey[i].name = "";
6354 ku = i;
6355 ekey[i++].label = "";
6357 ekey[i].ch = KEY_DOWN;
6358 ekey[i].rval = 31;
6359 ekey[i].name = "";
6360 ekey[i++].label = "";
6362 ekey[i].ch = -1;
6364 flags = OE_APPEND_CURRENT | OE_KEEP_TRAILING_SPACE;
6365 while(1){
6368 * 2 is really 1 because there will be one real entry and
6369 * one entry of "" because of the get_prev_hist above.
6371 if(items_in_hist(history) > 2){
6372 ekey[ku].name = HISTORY_UP_KEYNAME;
6373 ekey[ku].label = HISTORY_KEYLABEL;
6374 ekey[ku+1].name = HISTORY_DOWN_KEYNAME;
6375 ekey[ku+1].label = HISTORY_KEYLABEL;
6377 else{
6378 ekey[ku].name = "";
6379 ekey[ku].label = "";
6380 ekey[ku+1].name = "";
6381 ekey[ku+1].label = "";
6384 rc = optionally_enter(nsearch_string, command_line, 0,
6385 sizeof(nsearch_string),
6386 prompt, ekey, help, &flags);
6387 if(rc == 3){
6388 help = help == NO_HELP ? h_oe_searchab : NO_HELP;
6389 continue;
6391 else if(rc == 10){
6392 *warped = 1;
6393 warp_to_beginning(); /* go to top of addrbooks */
6394 if((nl=first_selectable_line(0L)) != NO_LINE){
6395 *new_line = nl;
6396 q_status_message(SM_INFO, 0, 2, _("Searched to first entry"));
6397 return 0;
6399 else{
6400 q_status_message(SM_INFO, 0, 2, _("No entries"));
6401 return -1;
6404 else if(rc == 11){
6405 *warped = 1;
6406 warp_to_end(); /* go to bottom */
6407 if((nl=first_selectable_line(0L)) != NO_LINE){
6408 *new_line = nl;
6409 q_status_message(SM_INFO, 0, 2, _("Searched to last entry"));
6410 return 0;
6412 else{
6413 q_status_message(SM_INFO, 0, 2, _("No entries"));
6414 return -1;
6417 else if(rc == 30){
6418 if((p = get_prev_hist(history, nsearch_string, 0, NULL)) != NULL){
6419 strncpy(nsearch_string, p, sizeof(nsearch_string));
6420 nsearch_string[sizeof(nsearch_string)-1] = '\0';
6422 else
6423 Writechar(BELL, 0);
6425 continue;
6427 else if(rc == 31){
6428 if((p = get_next_hist(history, nsearch_string, 0, NULL)) != NULL){
6429 strncpy(nsearch_string, p, sizeof(nsearch_string));
6430 nsearch_string[sizeof(nsearch_string)-1] = '\0';
6432 else
6433 Writechar(BELL, 0);
6435 continue;
6438 if(rc != 4){ /* 4 is redraw */
6439 save_hist(history, nsearch_string, 0, NULL);
6440 break;
6445 if(rc == 1 || (search_string[0] == '\0' && nsearch_string[0] == '\0'))
6446 return -2;
6448 if(nsearch_string[0] != '\0'){
6449 strncpy(search_string, nsearch_string, sizeof(search_string)-1);
6450 search_string[sizeof(search_string)-1] = '\0';
6453 find_result = find_in_book(cur_line, search_string, new_line, wrapped);
6455 if(*wrapped == 1)
6456 *warped = 1;
6458 if(find_result){
6459 int also = 0, notdisplayed = 0;
6461 pab = &as.adrbks[adrbk_num_from_lineno(*new_line)];
6462 if(find_result & MATCH_NICK){
6463 if(pab->nick_is_displayed)
6464 also++;
6465 else
6466 notdisplayed++;
6469 if(find_result & MATCH_FULL){
6470 if(pab->full_is_displayed)
6471 also++;
6472 else
6473 notdisplayed++;
6476 if(find_result & MATCH_ADDR){
6477 if(pab->addr_is_displayed)
6478 also++;
6479 else
6480 notdisplayed++;
6483 if(find_result & MATCH_FCC){
6484 if(pab->fcc_is_displayed)
6485 also++;
6486 else
6487 notdisplayed++;
6490 if(find_result & MATCH_COMMENT){
6491 if(pab->comment_is_displayed)
6492 also++;
6493 else
6494 notdisplayed++;
6497 if(find_result & MATCH_LISTMEM){
6498 AddrScrn_Disp *dl;
6500 dl = dlist(*new_line);
6501 if(F_OFF(F_EXPANDED_DISTLISTS,ps_global)
6502 && !exp_is_expanded(pab->address_book->exp, (a_c_arg_t)dl->elnum))
6503 notdisplayed++;
6506 if(notdisplayed > 1 && *wrapped == 0){
6507 if(also)
6508 /* TRANSLATORS: These "matched" messages are advisory messages explaining
6509 how a search command matched an entry */
6510 q_status_message1(SM_ORDER,0,4, _("Also matched string in %s other fields"),
6511 comatose(notdisplayed));
6512 else
6513 q_status_message1(SM_ORDER,0,4, _("Matched string in %s fields"),
6514 comatose(notdisplayed));
6516 else if(notdisplayed == 1 && *wrapped == 0){
6517 if(also){
6518 if(find_result & MATCH_NICK && !pab->nick_is_displayed)
6519 q_status_message(SM_ORDER,0,4, _("Also matched string in Nickname field"));
6520 else if(find_result & MATCH_FULL && !pab->full_is_displayed)
6521 q_status_message(SM_ORDER,0,4, _("Also matched string in Fullname field"));
6522 else if(find_result & MATCH_ADDR && !pab->addr_is_displayed)
6523 q_status_message(SM_ORDER,0,4, _("Also matched string in Address field"));
6524 else if(find_result & MATCH_FCC && !pab->fcc_is_displayed)
6525 q_status_message(SM_ORDER,0,4, _("Also matched string in Fcc field"));
6526 else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed)
6527 q_status_message(SM_ORDER,0,4, _("Also matched string in Comment field"));
6528 else if(find_result & MATCH_LISTMEM)
6529 q_status_message(SM_ORDER,0,4, _("Also matched string in list member address"));
6530 else
6531 q_status_message(SM_ORDER,0,4, _("Also matched string in ?"));
6533 else{
6534 if(find_result & MATCH_NICK && !pab->nick_is_displayed)
6535 q_status_message(SM_ORDER,0,4, _("Matched string in Nickname field"));
6536 else if(find_result & MATCH_FULL && !pab->full_is_displayed)
6537 q_status_message(SM_ORDER,0,4, _("Matched string in Fullname field"));
6538 else if(find_result & MATCH_ADDR && !pab->addr_is_displayed)
6539 q_status_message(SM_ORDER,0,4, _("Matched string in Address field"));
6540 else if(find_result & MATCH_FCC && !pab->fcc_is_displayed)
6541 q_status_message(SM_ORDER,0,4, _("Matched string in Fcc field"));
6542 else if(find_result & MATCH_COMMENT && !pab->comment_is_displayed)
6543 q_status_message(SM_ORDER,0,4, _("Matched string in Comment field"));
6544 else if(find_result & MATCH_LISTMEM)
6545 q_status_message(SM_ORDER,0,4, _("Matched string in list member address"));
6546 else
6547 q_status_message(SM_ORDER,0,4, _("Matched string in ?"));
6552 /* be sure to be on a selectable field */
6553 if(!line_is_selectable(*new_line))
6554 if((nl=first_selectable_line(*new_line+1)) != NO_LINE)
6555 *new_line = nl;
6558 return(find_result ? 0 : -1);
6563 * Search the display list for the given string.
6565 * Args: cur_line -- The current line position (in global display list)
6566 * of cursor
6567 * string -- String to search for
6568 * new_line -- Return value: new line position
6569 * wrapped -- Wrapped to beginning of display during search
6571 * Result: The new line number is set if the search is successful.
6572 * Returns 0 -- string not found
6573 * Otherwise, a bitmask of which fields the string was found in.
6576 find_in_book(long int cur_line, char *string, long int *new_line, int *wrapped)
6578 register AddrScrn_Disp *dl;
6579 long nl, nl_save;
6580 int fields;
6581 AdrBk_Entry *abe;
6582 char *listaddr = NULL;
6583 DL_CACHE_S *dlc,
6584 dlc_save; /* a local copy */
6587 dprint((9, "- find_in_book -\n"));
6590 * Save info to allow us to get back to where we were if we can't find
6591 * the string. Also used to stop our search if we wrap back to the
6592 * start and search forward.
6595 nl_save = cur_line;
6596 dlc = get_dlc(nl_save);
6597 dlc_save = *dlc;
6599 *wrapped = 0;
6600 nl = cur_line + 1L;
6602 /* start with next line and search to the end of the disp_list */
6603 dl = dlist(nl);
6604 while(dl->type != End){
6605 if(dl->type == Simple ||
6606 dl->type == ListHead ||
6607 dl->type == ListEnt ||
6608 dl->type == ListClickHere){
6609 abe = ae(nl);
6610 if(dl->type == ListEnt)
6611 listaddr = listmem(nl);
6613 else
6614 abe = (AdrBk_Entry *)NULL;
6616 if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0)
6617 goto found;
6619 dl = dlist(++nl);
6624 * Wrap back to the start of the addressbook and search forward
6625 * from there.
6627 warp_to_beginning(); /* go to top of addrbooks */
6628 nl = 0L; /* line number is always 0 after warp_to_beginning */
6629 *wrapped = 1;
6631 dlc = get_dlc(nl);
6632 while(!matching_dlcs(&dlc_save, dlc) && dlc->type != DlcEnd){
6634 fill_in_dl_field(dlc);
6635 dl = &dlc->dl;
6637 if(dl->type == Simple ||
6638 dl->type == ListHead ||
6639 dl->type == ListEnt ||
6640 dl->type == ListClickHere){
6641 abe = ae(nl);
6642 if(dl->type == ListEnt)
6643 listaddr = listmem(nl);
6645 else
6646 abe = (AdrBk_Entry *)NULL;
6648 if((fields=search_in_one_line(dl, abe, listaddr, string)) != 0)
6649 goto found;
6651 dlc = get_dlc(++nl);
6654 /* see if it is in the current line */
6655 fill_in_dl_field(dlc);
6656 dl = &dlc->dl;
6658 if(dl->type == Simple ||
6659 dl->type == ListHead ||
6660 dl->type == ListEnt ||
6661 dl->type == ListClickHere){
6662 abe = ae(nl);
6663 if(dl->type == ListEnt)
6664 listaddr = listmem(nl);
6666 else
6667 abe = (AdrBk_Entry *)NULL;
6669 fields = search_in_one_line(dl, abe, listaddr, string);
6670 if(dl->usst &&
6671 (dl->type == Text || dl->type == Title || dl->type == TitleCmb))
6672 fs_give((void **)&dl->usst);
6674 /* jump cache back to where we started */
6675 *wrapped = 0;
6676 warp_to_dlc(&dlc_save, nl_save);
6677 if(fields){
6678 *new_line = nl_save; /* because it was in current line */
6679 *wrapped = 2;
6682 nl = *new_line;
6684 found:
6685 *new_line = nl;
6686 return(fields);
6691 * Look in line dl for string.
6693 * Args: dl -- the display list for this line
6694 * abe -- AdrBk_Entry if it is an address type
6695 * listaddr -- list member if it is of type ListEnt
6696 * string -- look for this string
6698 * Result: 0 -- string not found
6699 * Otherwise, a bitmask of which fields the string was found in.
6700 * MATCH_NICK 0x1
6701 * MATCH_FULL 0x2
6702 * MATCH_ADDR 0x4
6703 * MATCH_FCC 0x8
6704 * MATCH_COMMENT 0x10
6705 * MATCH_BIGFIELD 0x20
6706 * MATCH_LISTMEM 0x40
6709 search_in_one_line(AddrScrn_Disp *dl, AdrBk_Entry *abe, char *listaddr, char *string)
6711 register int c;
6712 int ret_val = 0;
6713 char **lm;
6715 for(c = 0; c < 5; c++){
6716 switch(c){
6717 case 0:
6718 switch(dl->type){
6719 case Simple:
6720 case ListHead:
6721 if(srchstr(abe->nickname, string))
6722 ret_val |= MATCH_NICK;
6724 break;
6726 case Text:
6727 case Title:
6728 case TitleCmb:
6729 case AskServer:
6730 if(srchstr(dl->usst, string))
6731 ret_val |= MATCH_BIGFIELD;
6733 default:
6734 break;
6736 break;
6738 case 1:
6739 switch(dl->type){
6740 case Simple:
6741 case ListHead:
6742 if(abe && srchstr(
6743 (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
6744 SIZEOF_20KBUF, abe->fullname),
6745 string))
6746 ret_val |= MATCH_FULL;
6748 default:
6749 break;
6751 break;
6753 case 2:
6754 switch(dl->type){
6755 case Simple:
6756 if(srchstr((abe && abe->tag == Single) ?
6757 abe->addr.addr : NULL, string))
6758 ret_val |= MATCH_ADDR;
6760 break;
6762 case ListEnt:
6763 if(srchstr(
6764 (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf,
6765 SIZEOF_20KBUF, listaddr), string))
6766 ret_val |= MATCH_LISTMEM;
6768 break;
6770 case ListClickHere:
6771 if(abe)
6772 for(lm = abe->addr.list;
6773 !(ret_val & MATCH_LISTMEM) && *lm; lm++)
6774 if(srchstr(*lm, string))
6775 ret_val |= MATCH_LISTMEM;
6777 break;
6779 case Empty:
6780 case ListEmpty:
6781 if(srchstr(EMPTY, string))
6782 ret_val |= MATCH_BIGFIELD;
6784 break;
6786 case AddFirstPers:
6787 if(srchstr(ADD_PERSONAL, string))
6788 ret_val |= MATCH_BIGFIELD;
6790 break;
6792 case AddFirstGlob:
6793 if(srchstr(ADD_GLOBAL, string))
6794 ret_val |= MATCH_BIGFIELD;
6796 break;
6798 case NoAbooks:
6799 if(srchstr(NOABOOKS, string))
6800 ret_val |= MATCH_BIGFIELD;
6802 break;
6804 default:
6805 break;
6807 break;
6809 case 3: /* fcc */
6810 switch(dl->type){
6811 case Simple:
6812 case ListHead:
6813 if(abe && srchstr(abe->fcc, string))
6814 ret_val |= MATCH_FCC;
6816 default:
6817 break;
6819 break;
6821 case 4: /* comment */
6822 switch(dl->type){
6823 case Simple:
6824 case ListHead:
6825 if(abe){
6826 size_t n, len;
6827 unsigned char *p, *tmp = NULL;
6829 if((n = 4*strlen(abe->extra)) > SIZEOF_20KBUF-1){
6830 len = n+1;
6831 p = tmp = (unsigned char *)fs_get(len * sizeof(char));
6833 else{
6834 len = SIZEOF_20KBUF;
6835 p = (unsigned char *)tmp_20k_buf;
6838 if(srchstr((char *)rfc1522_decode_to_utf8(p, len, abe->extra),
6839 string))
6840 ret_val |= MATCH_COMMENT;
6842 if(tmp)
6843 fs_give((void **)&tmp);
6847 default:
6848 break;
6850 break;
6854 return(ret_val);
6859 * These chars in nicknames will mess up parsing.
6861 * Returns 0 if ok, 1 if not.
6862 * Returns an allocated error message on error.
6865 nickname_check(char *nickname, char **error)
6867 register char *t;
6868 char buf[100];
6870 if((t = strindex(nickname, SPACE)) ||
6871 (t = strindex(nickname, ',')) ||
6872 (t = strindex(nickname, '"')) ||
6873 (t = strindex(nickname, ';')) ||
6874 (t = strindex(nickname, ':')) ||
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 char s[4];
6884 s[0] = '"';
6885 s[1] = *t;
6886 s[2] = '"';
6887 s[3] = '\0';
6888 if(error){
6890 * TRANSLATORS: this is telling the user that one of the characters
6891 * they have included in a nickname will not work. It will say something like
6892 * Blank spaces not allowed in nicknames or
6893 * Commas not...
6894 * etc.
6896 snprintf(buf, sizeof(buf), _("%s not allowed in nicknames"),
6897 *t == SPACE ?
6898 _("Blank spaces") :
6899 *t == ',' ?
6900 _("Commas") :
6901 *t == '"' ?
6902 _("Quotes") :
6904 buf[sizeof(buf)-1] = '\0';
6905 *error = cpystr(buf);
6908 return 1;
6911 return 0;
6915 char *
6916 abook_select_screen(struct pine *ps)
6918 CONF_S *ctmp = NULL, *first_line = NULL;
6919 OPT_SCREEN_S screen;
6920 int adrbknum;
6921 char *helptitle;
6922 HelpType help;
6923 PerAddrBook *pab;
6924 char *abook = NULL;
6926 helptitle = _("HELP FOR SELECTING AN ADDRESS BOOK");
6927 help = h_role_abook_select;
6929 init_ab_if_needed();
6931 for(adrbknum = 0; adrbknum < as.n_addrbk; adrbknum++){
6932 new_confline(&ctmp);
6933 if(!first_line)
6934 first_line = ctmp;
6936 pab = &as.adrbks[adrbknum];
6938 ctmp->value = cpystr((pab && pab->abnick)
6939 ? pab->abnick
6940 : (pab && pab->filename)
6941 ? pab->filename
6942 : "?");
6944 ctmp->d.b.selected = &abook;
6945 ctmp->d.b.abookname = ctmp->value;
6946 ctmp->keymenu = &abook_select_km;
6947 ctmp->help = help;
6948 ctmp->help_title = helptitle;
6949 ctmp->tool = abook_select_tool;
6950 ctmp->flags = CF_STARTITEM;
6951 ctmp->valoffset = 4;
6954 if(first_line){
6955 memset(&screen, 0, sizeof(screen));
6956 (void) conf_scroll_screen(ps, &screen, first_line,
6957 /* TRANSLATORS: Print something1 using something2.
6958 abooks is something1 */
6959 _("SELECT ADDRESS BOOK"), _("abooks"), 0, NULL);
6961 else
6962 q_status_message(SM_ORDER|SM_DING, 3, 3, _("No address books defined!"));
6964 ps->mangled_screen = 1;
6965 return(abook);
6970 abook_select_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
6972 int retval;
6974 switch(cmd){
6975 case MC_CHOICE :
6976 *((*cl)->d.b.selected) = cpystr((*cl)->d.b.abookname);
6977 retval = simple_exit_cmd(flags);
6978 break;
6980 case MC_EXIT :
6981 retval = simple_exit_cmd(flags);
6982 break;
6984 default:
6985 retval = -1;
6986 break;
6989 if(retval > 0)
6990 ps->mangled_body = 1;
6992 return(retval);
6997 * This isn't actually just a nickname completion anymore. The user types
6998 * in a prefix of either a nickname, or a full address, or the addr@mailbox
6999 * part of an address and we look in all of the address books for matches
7000 * like that. We return the longest unambiguous match in answer.
7002 * Args prefix -- The part of the "nickname" that has been typed so far
7003 * answer -- The answer is returned here.
7004 * tabtab -- If the answer returned from adrbk_list_of_completions is
7005 * ambiguous and tabtab is set, then offer up a
7006 * selector screen for all the possible answers.
7007 * flags -- ANC_AFTERCOMMA -- This means that the passed in
7008 * prefix may be a list of comma
7009 * separated addresses and we're only
7010 * completing the last one.
7012 * Returns 0 -- no matches at all
7013 * 1 -- more than one nickname (or address) begins with
7014 * the answer being returned
7015 * 2 -- the returned answer is a complete answer, either a
7016 * nickname or a complete address, and there are
7017 * no longer matches for the prefix
7019 * Allocated answer is returned in answer argument.
7020 * Caller needs to free the answer.
7023 abook_nickname_complete(char *prefix, char **answer, int tabtab, unsigned flags)
7025 int ambiguity;
7026 COMPLETE_S *completions, *cp;
7027 char *saved_beginning = NULL;
7028 char *potential_answer = NULL;
7030 wp_exit = wp_nobail = 0;
7032 /* there shouldn't be a case where answer is NULL */
7033 if(answer)
7034 *answer = NULL;
7037 * If we need to handle case where no prefix is passed in,
7038 * figure that out when we need it.
7040 if(!(prefix && prefix[0]))
7041 return(0);
7043 if(flags & ANC_AFTERCOMMA){
7044 char *lastnick;
7047 * Find last comma, save the part before that, operate
7048 * only on the last address.
7050 if((lastnick = strrchr(prefix ? prefix : "", ',')) != NULL){
7051 lastnick++;
7052 while(!(*lastnick & 0x80) && isspace((unsigned char) (*lastnick)))
7053 lastnick++;
7055 saved_beginning = cpystr(prefix);
7056 saved_beginning[lastnick-prefix] = '\0';
7057 prefix = lastnick;
7061 if(!(prefix && prefix[0]))
7062 return(0);
7064 completions = adrbk_list_of_completions(prefix,
7065 ps_global->cur_uid_stream, ps_global->cur_uid,
7066 ALC_INCLUDE_ADDRS | ((strlen(prefix) >= 3) ? ALC_INCLUDE_LDAP : 0));
7068 if(!completions)
7069 ambiguity = 0;
7070 else if(completions && completions->next)
7071 ambiguity = 1;
7072 else
7073 ambiguity = 2;
7075 if(ambiguity == 2){
7076 if(completions->full_address && completions->full_address[0])
7077 potential_answer = cpystr(completions->full_address);
7078 else if(completions->nickname && completions->nickname[0])
7079 potential_answer = cpystr(completions->nickname);
7080 else if(completions->addr && completions->addr[0])
7081 potential_answer = cpystr(completions->addr);
7082 else
7083 potential_answer = cpystr(prefix);
7085 /* answer is ambiguous and caller wants a choose list in that case */
7086 else if(ambiguity == 1 && tabtab){
7087 potential_answer = choose_an_address_with_this_prefix(completions);
7089 if(potential_answer)
7090 ambiguity = 2;
7091 else{
7092 ambiguity = 1;
7093 potential_answer = cpystr(prefix);
7096 else if(ambiguity == 1){
7097 int k;
7098 char cand1_kth_char, cand2_kth_char;
7099 char unambig[1000];
7101 /* find the longest unambiguous prefix */
7102 strncpy(unambig, prefix, sizeof(unambig));
7103 unambig[sizeof(unambig)-1] = '\0';
7104 k = strlen(unambig);
7107 * First verify that they all match through prefix. LDAP sometimes gives
7108 * weird, inexplicable answers that don't seem to match at all.
7110 for(cp = completions; cp; cp = cp->next)
7111 if(!( /* not a match */
7112 /* no NICK bit OR nickname matches prefix */
7113 (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && !struncmp(unambig, cp->nickname, k)))
7114 /* AND no ADDR bit OR addr matches prefix */
7115 && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && !struncmp(unambig, cp->addr, k)))
7116 /* AND neither FULL bit is set OR one of the two fulls matches prefix */
7117 && (!(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))))
7119 break;
7121 /* if cp that means there was not a universal match up through prefix, stop */
7122 if(!cp)
7124 cand1_kth_char = cand2_kth_char = '\0';
7125 if(completions->matches_bitmap & ALC_NICK && completions->nickname && strlen(completions->nickname) >= k)
7126 cand1_kth_char = completions->nickname[k];
7127 else if(completions->matches_bitmap & ALC_ADDR && completions->addr && strlen(completions->addr) >= k)
7128 cand1_kth_char = completions->addr[k];
7129 else{
7130 if(completions->matches_bitmap & ALC_FULL && completions->full_address && strlen(completions->full_address) >= k)
7131 cand1_kth_char = completions->full_address[k];
7133 if(completions->matches_bitmap & ALC_REVFULL && completions->rev_fullname && strlen(completions->rev_fullname) >= k)
7134 cand2_kth_char = completions->rev_fullname[k];
7138 * You'll want a wide screen to read this. There are two possible
7139 * candidate chars for the next position. One or the other of them
7140 * has to match in all of the possible completions. We consider it
7141 * a match if either of the fullname completions for this entry is
7142 * a match. That may not match what the user expects but it may.
7144 for(cp = completions; cp; cp = cp->next){
7145 if(!( /* candidate 1 is not a match */
7146 /* candidate 1 is defined */
7147 cand1_kth_char && cand1_kth_char != ','
7148 /* AND no NICK bit OR nickname char is a match */
7149 && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand1_kth_char))
7150 /* AND no ADDR bit OR addr char is a match */
7151 && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand1_kth_char))
7152 /* AND neither FULL bit is set OR one of the two full chars is a match */
7153 && (!(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)))
7155 cand1_kth_char = '\0'; /* mark that it isn't a match */
7157 if(!cand1_kth_char && !( /* cand1 is not a match AND cand2 is not a match */
7158 /* candidate 2 is defined */
7159 cand2_kth_char && cand2_kth_char != ','
7160 /* AND no NICK bit OR nickname char is a match */
7161 && (!(cp->matches_bitmap & ALC_NICK) || (cp->nickname && strlen(cp->nickname) >= k && cp->nickname[k] == cand2_kth_char))
7162 /* AND no ADDR bit OR addr char is a match */
7163 && (!(cp->matches_bitmap & ALC_ADDR) || (cp->addr && strlen(cp->addr) >= k && cp->addr[k] == cand2_kth_char))
7164 /* AND neither FULL bit is set OR one of the two full chars is a match */
7165 && (!(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)))
7167 cand2_kth_char = '\0'; /* mark that it isn't a match */
7169 if(!cand1_kth_char && !cand2_kth_char)
7170 break; /* no match so break */
7173 if(!cp) /* they all matched */
7174 unambig[k++] = cand1_kth_char ? cand1_kth_char : cand2_kth_char;
7176 }while(!cp && k < sizeof(unambig)-1);
7178 unambig[k] = '\0';
7179 unambig[sizeof(unambig)-1] = '\0';
7181 /* don't return answer with trailing space */
7182 while(--k >= 0 && isspace((unsigned char) unambig[k]))
7183 unambig[k] = '\0';
7185 potential_answer = cpystr(unambig);
7188 if(completions)
7189 free_complete_s(&completions);
7191 if(answer && ambiguity != 0){
7192 if(potential_answer){
7193 if(saved_beginning){
7194 size_t l1, l2;
7196 l1 = strlen(saved_beginning);
7197 l2 = strlen(potential_answer);
7198 *answer = (char *) fs_get((l1+l2+1) * sizeof(char));
7199 strncpy(*answer, saved_beginning, l1+l2);
7200 strncpy(*answer+l1, potential_answer, l2);
7201 (*answer)[l1+l2] = '\0';
7203 else{
7204 *answer = potential_answer;
7205 potential_answer = NULL;
7208 else{
7209 /* this can't happen */
7210 ambiguity = 0;
7214 if(saved_beginning)
7215 fs_give((void **) &saved_beginning);
7217 if(potential_answer)
7218 fs_give((void **) &potential_answer);
7220 return(ambiguity);
7225 * Returns an allocated nickname choice from user that begins with
7226 * prefix.
7228 char *
7229 choose_an_address_with_this_prefix(COMPLETE_S *completions)
7231 char buf[1000];
7232 char *chosen_address = NULL;
7233 char **lp, **da, **possible_addrs = NULL, **display_addrs = NULL;
7234 COMPLETE_S *cp;
7235 size_t cnt = 0;
7236 int show_nick, show_revfull;
7239 * Count how many and allocate an array for choose_item_from_list().
7241 for(cnt = 0, cp = completions; cp; cp = cp->next)
7242 cnt++;
7245 * Copy completions into an array.
7247 if(cnt > 0){
7248 lp = possible_addrs = (char **) fs_get((cnt+1) * sizeof(*possible_addrs));
7249 memset(possible_addrs, 0, (cnt+1) * sizeof(*possible_addrs));
7250 da = display_addrs = (char **) fs_get((cnt+1) * sizeof(*display_addrs));
7251 memset(display_addrs, 0, (cnt+1) * sizeof(*display_addrs));
7252 for(cp = completions; cp; cp = cp->next){
7253 show_nick = (cp->matches_bitmap & ALC_NICK) && cp->nickname && cp->nickname[0];
7254 show_revfull = 0;
7255 if(!show_nick && !(cp->matches_bitmap & (ALC_NICK | ALC_ADDR | ALC_FULL))
7256 && (cp->matches_bitmap & ALC_REVFULL)
7257 && cp->rev_fullname && cp->rev_fullname[0])
7258 show_revfull = 1;
7260 snprintf(buf, sizeof(buf), "%s%s%s%s%s",
7261 cp->full_address ? cp->full_address : "?",
7262 (show_nick || show_revfull) ? " (" : "",
7263 show_nick ? cp->nickname : "",
7264 show_revfull ? cp->rev_fullname : "",
7265 (show_nick || show_revfull) ? ")" : "");
7266 *da++ = cpystr(buf);
7267 *lp++ = cpystr(cp->full_address ? cp->full_address : "?");
7271 if(possible_addrs){
7272 chosen_address = choose_item_from_list(possible_addrs, display_addrs,
7273 _("SELECT AN ADDRESS"),
7274 _("addresses"),
7275 h_select_address_screen,
7276 _("HELP FOR SELECTING AN ADDRESS"),
7277 NULL);
7278 free_list_array(&possible_addrs);
7281 if(display_addrs)
7282 free_list_array(&display_addrs);
7284 return(chosen_address);
7288 #ifdef _WINDOWS
7290 * addr_scroll_up - adjust the global screen state struct such that pine's
7291 * window on the data is shifted DOWN (i.e., the data's
7292 * scrolled up).
7295 addr_scroll_up(count)
7296 long count;
7298 int next;
7300 if(count < 0)
7301 return(addr_scroll_down(-count));
7302 else if(count){
7303 long i;
7305 i=count;
7306 as.cur_row += as.top_ent;
7307 while(i && as.top_ent + 1 < as.last_ent){
7308 if(line_is_selectable(as.top_ent)){
7309 if(next_selectable_line(as.top_ent,&next)){
7310 as.cur_row = next;
7311 i--;
7312 as.top_ent++;
7314 else i = 0;
7316 else {
7317 i--;
7318 as.top_ent++;
7321 as.cur_row = as.cur_row - as.top_ent; /* must always be positive */
7323 as.old_cur_row = as.cur_row;
7326 return(1);
7331 * addr_scroll_down - adjust the global screen state struct such that pine's
7332 * window on the data is shifted UP (i.e., the data's
7333 * scrolled down).
7336 addr_scroll_down(count)
7337 long count;
7339 if(count < 0)
7340 return(addr_scroll_up(-count));
7341 else if(count){
7342 long i;
7344 for(i = count; i && as.top_ent; i--, as.top_ent--)
7345 as.cur_row++;
7347 while (as.cur_row >= as.l_p_page){
7348 prev_selectable_line(as.cur_row+as.top_ent, &as.cur_row);
7349 as.cur_row = as.cur_row - as.top_ent;
7352 as.old_cur_row = as.cur_row;
7355 return(1);
7360 * addr_scroll_to_pos - scroll the address book data in pine's window such
7361 * tthat the given "line" is at the top of the page.
7364 addr_scroll_to_pos(line)
7365 long line;
7367 return(addr_scroll_up(line - as.top_ent));
7371 /*----------------------------------------------------------------------
7372 MSWin scroll callback. Called during scroll message processing.
7376 Args: cmd - what type of scroll operation.
7377 scroll_pos - parameter for operation.
7378 used as position for SCROLL_TO operation.
7380 Returns: TRUE - did the scroll operation.
7381 FALSE - was not able to do the scroll operation.
7382 ----*/
7384 addr_scroll_callback (cmd, scroll_pos)
7385 int cmd;
7386 long scroll_pos;
7388 int paint = TRUE;
7390 switch (cmd) {
7391 case MSWIN_KEY_SCROLLUPLINE:
7392 paint = addr_scroll_down (scroll_pos);
7393 break;
7395 case MSWIN_KEY_SCROLLDOWNLINE:
7396 paint = addr_scroll_up (scroll_pos);
7397 break;
7399 case MSWIN_KEY_SCROLLUPPAGE:
7400 paint = addr_scroll_down (as.l_p_page);
7401 break;
7403 case MSWIN_KEY_SCROLLDOWNPAGE:
7404 paint = addr_scroll_up (as.l_p_page);
7405 break;
7407 case MSWIN_KEY_SCROLLTO:
7408 paint = addr_scroll_to_pos (scroll_pos);
7409 break;
7412 if(paint)
7413 display_book(0, as.cur_row, -1, 1, (Pos *)NULL);
7415 return(paint);
7419 char *
7420 pcpine_help_addrbook(title)
7421 char *title;
7424 * Title is size 256. Fix this to pass the titlelen.
7426 if(title)
7427 strncpy(title, (as.config)
7428 ? _("Alpine CONFIGURING ADDRESS BOOKS Help")
7429 : _("Alpine ADDRESS_BOOK Help"), 256);
7431 return(pcpine_help(gAbookHelp));
7433 #endif /* _WINDOWS */