Add support for tab-completion when selecting by rule
[alpine.git] / pico / composer.c
blob65408aa06b12c47e0bc3e3c90a23dc871716612f
1 /*
2 * ========================================================================
3 * Copyright 2006-2009 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
14 * Program: Pine composer routines
16 * NOTES:
18 * - composer.c is the composer for the PINE mail system
20 * - tabled 01/19/90
22 * Notes: These routines aren't incorporated yet, because the composer as
23 * a whole still needs development. These are ideas that should
24 * be implemented in later releases of PINE. See the notes in
25 * pico.c concerning what needs to be done ....
27 * - untabled 01/30/90
29 * Notes: Installed header line editing, with wrapping and unwrapping,
30 * context sensitive help, and other mail header editing features.
32 * - finalish code cleanup 07/15/91
34 * Notes: Killed/yanked header lines use emacs kill buffer.
35 * Arbitrarily large headers are handled gracefully.
36 * All formatting handled by FormatLines.
38 * - Work done to optimize display painting 06/26/92
39 * Not as clean as it should be, needs more thought
42 #include "headers.h"
44 #include "osdep/terminal.h"
45 #include "../pith/string.h"
47 int InitEntryText(char *, struct headerentry *);
48 int HeaderOffset(int);
49 int HeaderFocus(int, int);
50 UCS LineEdit(int, UCS *);
51 int header_downline(int, int);
52 int header_upline(int);
53 int FormatLines(struct hdr_line *, char *, int, int, int);
54 int FormatSyncAttach(void);
55 UCS *ucs4_strqchr(UCS *, UCS, int *, int);
56 int ComposerHelp(int);
57 void NewTop(int);
58 void display_delimiter(int);
59 void zotentry(struct hdr_line *);
60 int InvertPrompt(int, int);
61 int partial_entries(void);
62 int physical_line(struct hdr_line *);
63 int strend(UCS *, UCS);
64 int KillHeaderLine(struct hdr_line *, int);
65 int SaveHeaderLines(void);
66 UCS *break_point(UCS *, int, UCS, int *);
67 int hldelete(struct hdr_line *);
68 int is_blank(int, int, int);
69 int zotcomma(UCS *);
70 struct hdr_line *first_hline(int *);
71 struct hdr_line *first_sel_hline(int *);
72 struct hdr_line *next_hline(int *, struct hdr_line *);
73 struct hdr_line *next_sel_hline(int *, struct hdr_line *);
74 struct hdr_line *prev_hline(int *, struct hdr_line *);
75 struct hdr_line *prev_sel_hline(int *, struct hdr_line *);
76 struct hdr_line *first_requested_hline(int *);
77 void fix_mangle_and_err(int *, char **, char *);
78 void mark_sticky(struct headerentry *);
82 * definition header field array, structures defined in pico.h
84 struct headerentry *headents;
88 * structure that keeps track of the range of header lines that are
89 * to be displayed and other fun stuff about the header
91 struct on_display ods; /* global on_display struct */
93 /* a pointer to the subject line. This is so that we do not have to compute
94 * the header line in every call. It saves a few CPU cycles
97 struct hdr_line *subject_line = NULL;
100 * useful macros
102 #define HALLOC() (struct hdr_line *)malloc(sizeof(struct hdr_line))
103 #define LINEWID() (((term.t_ncol - headents[ods.cur_e].prwid) > 0) ? ((unsigned) (term.t_ncol - headents[ods.cur_e].prwid)) : 0)
104 #define BOTTOM() (term.t_nrow - term.t_mrow)
105 #define FULL_SCR() (BOTTOM() - 3)
106 #define HALF_SCR() (FULL_SCR()/2)
108 #ifdef MOUSE
110 * Redefine HeaderEditor to install wrapper required for mouse event
111 * handling...
113 #define HeaderEditor HeaderEditorWork
114 #endif /* MOUSE */
116 #define HDR_DELIM "----- Message Text -----"
119 * useful declarations
121 static short delim_ps = 0; /* previous state */
122 static short invert_ps = 0; /* previous state */
125 static KEYMENU menu_header[] = {
126 /* TRANSLATORS: command key labels, Send means send the message
127 we are currently working on. */
128 {"^G", N_("Get Help"), KS_SCREENHELP}, {"^X", N_("Send"), KS_SEND},
129 /* TRANSLATORS: Rich Headers is a command to display more headers. It
130 is triggered with the ^R key. PrvPg stands for Previous Page. */
131 {"^R", N_("Rich Hdr"), KS_RICHHDR}, {"^Y", N_("PrvPg/Top"), KS_PREVPAGE},
132 /* TRANSLATORS: Cut Line means remove a line. Postpone means to save
133 a message being composed so that it can be worked on later. */
134 {"^K", N_("Cut Line"), KS_CURPOSITION}, {"^O", N_("Postpone"), KS_POSTPONE},
135 /* TRANSLATORS: Del Char is Delete Character */
136 {"^C", N_("Cancel"), KS_CANCEL}, {"^D", N_("Del Char"), KS_NONE},
137 /* TRANSLATORS: Next Page */
138 {"^J", N_("Attach"), KS_ATTACH}, {"^V", N_("NxtPg/End"), KS_NEXTPAGE},
139 /* TRANSLATORS: Undelete a line that was just deleted */
140 {"^U", N_("UnDel Line"), KS_NONE}, {NULL, NULL}
142 #define SEND_KEY 1
143 #define RICH_KEY 2
144 #define CUT_KEY 4
145 #define PONE_KEY 5
146 #define DEL_KEY 7
147 #define ATT_KEY 8
148 #define UDEL_KEY 10
149 #define TO_KEY 11
153 * function key mappings for header editor
155 static UCS ckm[12][2] = {
156 { F1, (CTRL|'G')},
157 { F2, (CTRL|'C')},
158 { F3, (CTRL|'X')},
159 { F4, (CTRL|'D')},
160 { F5, (CTRL|'R')},
161 { F6, (CTRL|'J')},
162 { F7, 0 },
163 { F8, 0 },
164 { F9, (CTRL|'K')},
165 { F10, (CTRL|'U')},
166 { F11, (CTRL|'O')},
167 { F12, (CTRL|'T')}
172 * InitMailHeader - initialize header array, and set beginning editor row
173 * range. The header entry structure should look just like
174 * what is written on the screen, the vector
175 * (entry, line, offset) will describe the current cursor
176 * position in the header.
178 * Returns: TRUE if special header handling was requested,
179 * FALSE under standard default behavior.
182 InitMailHeader(PICO *mp)
184 char *addrbuf;
185 struct headerentry *he;
186 int rv;
188 if(!mp->headents){
189 headents = NULL;
190 return(FALSE);
194 * initialize some of on_display structure, others below...
196 ods.p_ind = 0;
197 ods.p_line = COMPOSER_TOP_LINE;
198 ods.top_l = ods.cur_l = NULL;
200 headents = mp->headents;
201 /*--- initialize the fields in the headerent structure ----*/
202 for(he = headents; he->name != NULL; he++){
203 he->hd_text = NULL;
204 he->display_it = he->display_it ? he->display_it : !he->rich_header;
205 if(he->is_attach) {
206 /*--- A lot of work to do since attachments are special ---*/
207 he->maxlen = 0;
208 if(mp->attachments != NULL){
209 char buf[NLINE];
210 int attno = 0;
211 int l1, ofp, ofp1, ofp2; /* OverFlowProtection */
212 size_t addrbuflen = 4 * NLINE; /* malloc()ed size of addrbuf */
213 PATMT *ap = mp->attachments;
215 ofp = NLINE - 35;
217 addrbuf = (char *)malloc(addrbuflen);
218 addrbuf[0] = '\0';
219 buf[0] = '\0';
220 while(ap){
221 if((l1 = strlen(ap->filename)) <= ofp){
222 ofp1 = l1;
223 ofp2 = ofp - l1;
225 else{
226 ofp1 = ofp;
227 ofp2 = 0;
230 if(ap->filename){
231 snprintf(buf, sizeof(buf), "%d. %.*s%s %s%s%s\"",
232 ++attno,
233 ofp1,
234 ap->filename,
235 (l1 > ofp) ? "...]" : "",
236 ap->size ? "(" : "",
237 ap->size ? ap->size : "",
238 ap->size ? ") " : "");
240 /* append description, escaping quotes */
241 if(ap->description){
242 char *dp = ap->description, *bufp = &buf[strlen(buf)];
243 int escaped = 0;
246 if(*dp == '\"' && !escaped){
247 *bufp++ = '\\';
248 ofp2--;
250 else if(escaped){
251 *bufp++ = '\\';
252 escaped = 0;
254 while(--ofp2 > 0 && (*bufp++ = *dp++));
256 *bufp = '\0';
259 snprintf(buf + strlen(buf), sizeof(buf)-strlen(buf), "\"%s", ap->next ? "," : "");
261 if(strlen(addrbuf) + strlen(buf) >= addrbuflen){
262 addrbuflen += NLINE * 4;
263 if(!(addrbuf = (char *)realloc(addrbuf, addrbuflen))){
264 emlwwrite(_("Can't realloc addrbuf to %d bytes"),
265 (void *) addrbuflen);
266 return(ABORT);
270 strncat(addrbuf, buf, addrbuflen-strlen(addrbuf)-1);
271 addrbuf[addrbuflen-1] = '\0';
273 ap = ap->next;
275 InitEntryText(addrbuf, he);
276 free((char *)addrbuf);
277 } else {
278 InitEntryText("", he);
280 he->realaddr = NULL;
281 } else {
282 if(!he->blank)
283 addrbuf = *(he->realaddr);
284 else
285 addrbuf = "";
287 InitEntryText(addrbuf, he);
292 * finish initialization and then figure out display layout.
293 * first, look for any field the caller requested we start in,
294 * and set the offset into that field.
296 if((ods.cur_l = first_requested_hline(&ods.cur_e)) != NULL){
297 ods.top_e = 0; /* init top_e */
298 ods.top_l = first_hline(&ods.top_e);
299 if(!HeaderFocus(ods.cur_e, Pmaster ? Pmaster->edit_offset : 0))
300 HeaderFocus(ods.cur_e, 0);
302 rv = TRUE;
304 else{
305 ods.top_l = first_hline(&ods.cur_e);
306 ods.cur_l = first_sel_hline(&ods.cur_e);
307 ods.top_e = ods.cur_e;
308 rv = 0;
311 UpdateHeader(0);
312 return(rv);
318 * InitEntryText - Add the given header text into the header entry
319 * line structure.
322 InitEntryText(char *utf8text, struct headerentry *e)
324 struct hdr_line *curline;
325 register int longest;
328 * get first chunk of memory, and tie it to structure...
330 if((curline = HALLOC()) == NULL){
331 emlwrite("Unable to make room for full Header.", NULL);
332 return(FALSE);
335 longest = term.t_ncol - e->prwid - 1;
336 curline->text[0] = '\0';
337 curline->next = NULL;
338 curline->prev = NULL;
339 e->hd_text = curline; /* tie it into the list */
341 if(FormatLines(curline, utf8text, longest, e->break_on_comma, 0) == -1)
342 return(FALSE);
343 else
344 return(TRUE);
350 * ResizeHeader - Handle resizing display when SIGWINCH received.
352 * notes:
353 * works OK, but needs thorough testing
357 ResizeHeader(void)
359 register struct headerentry *i;
360 register int offset;
362 if(!headents)
363 return(TRUE);
365 offset = (ComposerEditing) ? HeaderOffset(ods.cur_e) : 0;
367 for(i=headents; i->name; i++){ /* format each entry */
368 if(FormatLines(i->hd_text, "", (term.t_ncol - i->prwid),
369 i->break_on_comma, 0) == -1){
370 return(-1);
374 if(ComposerEditing) /* restart at the top */
375 HeaderFocus(ods.cur_e, offset); /* fix cur_l and p_ind */
376 else {
377 struct hdr_line *l;
378 int e;
380 for(i = headents; i->name != NULL; i++); /* Find last line */
381 i--;
382 e = i - headents;
383 l = headents[e].hd_text;
384 if(!headents[e].display_it || headents[e].blank)
385 l = prev_sel_hline(&e, l); /* last selectable line */
387 if(!l){
388 e = i - headents;
389 l = headents[e].hd_text;
392 HeaderFocus(e, -1); /* put focus on last line */
395 if(ComposerTopLine != COMPOSER_TOP_LINE)
396 UpdateHeader(0);
398 PaintBody(0);
400 if(ComposerEditing)
401 movecursor(ods.p_line, ods.p_ind+headents[ods.cur_e].prwid);
403 (*term.t_flush)();
404 return(TRUE);
410 * HeaderOffset - return the character offset into the given header
413 HeaderOffset(int h)
415 register struct hdr_line *l;
416 int i = 0;
418 l = headents[h].hd_text;
420 while(l != ods.cur_l){
421 i += ucs4_strlen(l->text);
422 l = l->next;
424 return(i+ods.p_ind);
430 * HeaderFocus - put the dot at the given offset into the given header
433 HeaderFocus(int h, int offset)
435 register struct hdr_line *l;
436 register int i;
437 int last = 0;
439 if(offset == -1) /* focus on last line */
440 last = 1;
442 l = headents[h].hd_text;
443 while(1){
444 if(last && l->next == NULL){
445 break;
447 else{
448 if((i=ucs4_strlen(l->text)) >= offset)
449 break;
450 else
451 offset -= i;
453 if((l = l->next) == NULL)
454 return(FALSE);
457 ods.cur_l = l;
458 ods.p_len = ucs4_strlen(l->text);
459 ods.p_ind = (last) ? 0 : offset;
461 return(TRUE);
467 * HeaderEditor() - edit the mail header field by field, trapping
468 * important key sequences, hand the hard work off
469 * to LineEdit().
470 * returns:
471 * -3 if we drop out bottom *and* want to process mouse event
472 * -1 if we drop out the bottom
473 * FALSE if editing is cancelled
474 * TRUE if editing is finished
477 HeaderEditor(int f, int n)
479 register int i;
480 UCS ch = 0, lastch;
481 register char *bufp;
482 struct headerentry *h;
483 int cur_e, mangled, retval = -1,
484 hdr_only = (gmode & MDHDRONLY) ? 1 : 0;
485 char *errmss, *err;
486 #ifdef MOUSE
487 MOUSEPRESS mp;
488 #endif
490 if(!headents)
491 return(TRUE); /* nothing to edit! */
493 ComposerEditing = TRUE;
494 display_delimiter(0); /* provide feedback */
496 #ifdef _WINDOWS
497 mswin_setscrollrange (0, 0);
498 #endif /* _WINDOWS */
500 if(Pmaster)
501 for (subject_line = NULL, i=0; headents[i].name; i++)
502 if(strcmp(headents[i].name, "Subject") == 0)
503 subject_line = headents[i].hd_text;
506 * Decide where to begin editing. if f == TRUE begin editing
507 * at the bottom. this case results from the cursor backing
508 * into the editor from the bottom. otherwise, the user explicitly
509 * requested editing the header, and we begin at the top.
511 * further, if f == 1, we moved into the header by hitting the up arrow
512 * in the message text, else if f == 2, we moved into the header by
513 * moving past the left edge of the top line in the message. so, make
514 * the end of the last line of the last entry the current cursor position
515 * lastly, if f == 3, we moved into the header by hitting backpage() on
516 * the top line of the message, so scroll a page back.
518 if(f){
519 if(f == 2){ /* 2 leaves cursor at end */
520 struct hdr_line *l = ods.cur_l;
521 int e = ods.cur_e;
523 /*--- make sure on last field ---*/
524 while((l = next_sel_hline(&e, l)) != NULL)
525 if(headents[ods.cur_e].display_it){
526 ods.cur_l = l;
527 ods.cur_e = e;
530 ods.p_ind = 1000; /* and make sure at EOL */
532 else{
534 * note: assumes that ods.cur_e and ods.cur_l haven't changed
535 * since we left...
538 /* fix position */
539 if(curwp->w_doto < headents[ods.cur_e].prwid)
540 ods.p_ind = 0;
541 else if(curwp->w_doto < ods.p_ind + headents[ods.cur_e].prwid)
542 ods.p_ind = curwp->w_doto - headents[ods.cur_e].prwid;
543 else
544 ods.p_ind = 1000;
546 /* and scroll back if needed */
547 if(f == 3)
548 for(i = 0; header_upline(0) && i <= FULL_SCR(); i++)
552 ods.p_line = ComposerTopLine - 2;
554 /* else just trust what ods contains */
556 InvertPrompt(ods.cur_e, TRUE); /* highlight header field */
557 sgarbk = 1;
560 lastch = ch;
561 if(km_popped){
562 km_popped--;
563 if(km_popped == 0)
564 sgarbk = 1;
567 if(sgarbk){
568 if(km_popped){ /* temporarily change to cause menu to paint */
569 term.t_mrow = 2;
570 curwp->w_ntrows -= 2;
571 movecursor(term.t_nrow-2, 0); /* clear status line, too */
572 peeol();
574 else if(term.t_mrow == 0)
575 PaintBody(1);
577 ShowPrompt(); /* display correct options */
578 sgarbk = 0;
579 if(km_popped){
580 term.t_mrow = 0;
581 curwp->w_ntrows += 2;
585 ch = LineEdit(!(gmode&MDVIEW), &lastch); /* work on the current line */
587 if(km_popped)
588 switch(ch){
589 case NODATA:
590 case (CTRL|'L'):
591 km_popped++;
592 break;
594 default:
595 movecursor(term.t_nrow-2, 0);
596 peeol();
597 movecursor(term.t_nrow-1, 0);
598 peeol();
599 movecursor(term.t_nrow, 0);
600 peeol();
601 break;
604 switch (ch){
605 case (CTRL|'R') : /* Toggle header display */
606 if(Pmaster->pine_flags & MDHDRONLY){
607 if(Pmaster->expander){
608 packheader();
609 call_expander();
610 PaintBody(0);
611 break;
613 else
614 goto bleep;
617 /*---- Are there any headers to expand above us? ---*/
618 for(h = headents; h != &headents[ods.cur_e]; h++)
619 if(h->rich_header)
620 break;
621 if(h->rich_header)
622 InvertPrompt(ods.cur_e, FALSE); /* Yes, don't leave inverted */
624 mangled = 0;
625 err = NULL;
626 if(partial_entries()){
627 /*--- Just turned off all rich headers --*/
628 if(headents[ods.cur_e].rich_header){
629 /*-- current header got turned off too --*/
630 #ifdef ATTACHMENTS
631 if(headents[ods.cur_e].is_attach){
632 /* verify the attachments */
633 if((i = FormatSyncAttach()) != 0){
634 FormatLines(headents[ods.cur_e].hd_text, "",
635 term.t_ncol - headents[ods.cur_e].prwid,
636 headents[ods.cur_e].break_on_comma, 0);
639 #endif
640 if(headents[ods.cur_e].builder) /* verify text */
641 i = call_builder(&headents[ods.cur_e], &mangled, &err)>0;
642 /* Check below */
643 for(cur_e =ods.cur_e; headents[cur_e].name!=NULL; cur_e++)
644 if(!headents[cur_e].rich_header)
645 break;
646 if(headents[cur_e].name == NULL) {
647 /* didn't find one, check above */
648 for(cur_e =ods.cur_e; headents[cur_e].name!=NULL;
649 cur_e--)
650 if(!headents[cur_e].rich_header)
651 break;
654 ods.p_ind = 0;
655 ods.cur_e = cur_e;
656 ods.cur_l = headents[ods.cur_e].hd_text;
660 ods.p_line = 0; /* force update */
661 UpdateHeader(0);
662 PaintHeader(COMPOSER_TOP_LINE, FALSE);
663 PaintBody(1);
664 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
665 break;
667 case (CTRL|'C') : /* bag whole thing ?*/
668 if(abort_composer(1, 0) == TRUE)
669 return(FALSE);
671 break;
673 case (CTRL|'X') : /* Done. Send it. */
674 i = 0;
675 #ifdef ATTACHMENTS
676 if(headents[ods.cur_e].is_attach){
677 /* verify the attachments, and pretty things up in case
678 * we come back to the composer due to error...
680 if((i = FormatSyncAttach()) != 0){
681 sleep(2); /* give time for error to absorb */
682 FormatLines(headents[ods.cur_e].hd_text, "",
683 term.t_ncol - headents[ods.cur_e].prwid,
684 headents[ods.cur_e].break_on_comma, 0);
687 else
688 #endif
689 mangled = 0;
690 err = NULL;
691 if(headents[ods.cur_e].builder) /* verify text? */
692 i = call_builder(&headents[ods.cur_e], &mangled, &err);
694 if(i < 0){ /* don't leave without a valid addr */
695 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
696 break;
698 else if(i > 0){
699 ods.cur_l = headents[ods.cur_e].hd_text; /* attach cur_l */
700 ods.p_ind = 0;
701 ods.p_line = 0; /* force realignment */
702 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
703 NewTop(0);
706 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
708 if(wquit(1,0) == TRUE)
709 return(TRUE);
711 if(i > 0){
713 * need to be careful here because pointers might be messed up.
714 * also, could do a better job of finding the right place to
715 * put the dot back (i.e., the addr/list that was expanded).
717 UpdateHeader(0);
718 PaintHeader(COMPOSER_TOP_LINE, FALSE);
719 PaintBody(1);
721 break;
723 case (CTRL|'Z') : /* Suspend compose */
724 if(gmode&MDSSPD){ /* is it allowed? */
725 bktoshell(0, 1);
726 PaintBody(0);
728 else
729 unknown_command(ch);
731 break;
733 case (CTRL|'\\') :
734 #if defined MOUSE && !defined(_WINDOWS)
735 toggle_xterm_mouse(0,1);
736 #else
737 unknown_command(ch);
738 #endif
739 break;
741 case (CTRL|'O') : /* Suspend message */
742 if(Pmaster->pine_flags & MDHDRONLY)
743 goto bleep;
745 i = 0;
746 mangled = 0;
747 err = NULL;
748 if(headents[ods.cur_e].is_attach){
749 if(FormatSyncAttach() < 0){
750 if(mlyesno_utf8(_("Problem with attachments. Postpone anyway?"),
751 FALSE) != TRUE){
752 if(FormatLines(headents[ods.cur_e].hd_text, "",
753 term.t_ncol - headents[ods.cur_e].prwid,
754 headents[ods.cur_e].break_on_comma, 0) == -1)
755 emlwwrite(_("Format lines failed!"), NULL);
756 UpdateHeader(0);
757 PaintHeader(COMPOSER_TOP_LINE, FALSE);
758 PaintBody(1);
759 continue;
763 else if(headents[ods.cur_e].builder)
764 i = call_builder(&headents[ods.cur_e], &mangled, &err);
766 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
768 if(i < 0) /* don't leave without a valid addr */
769 break;
771 suspend_composer(1, 0);
772 return(TRUE);
774 #ifdef ATTACHMENTS
775 case (CTRL|'J') : /* handle attachments */
776 if(Pmaster->pine_flags & MDHDRONLY)
777 goto bleep;
779 { char cmt[NLINE];
780 LMLIST *lm = NULL, *lmp;
781 char buf[NLINE], *bfp;
782 int saved_km_popped;
783 size_t len;
786 * Attachment questions mess with km_popped and assume
787 * it is zero to start with. If we don't set it to zero
788 * on entry, the message about mime type will be erased
789 * by PaintBody. If we don't reset it when we come back,
790 * the bottom three lines may be messed up.
792 saved_km_popped = km_popped;
793 km_popped = 0;
795 if(AskAttach(cmt, sizeof(cmt), &lm)){
797 for(lmp = lm; lmp; lmp = lmp->next){
798 size_t space;
800 len = lmp->dir ? strlen(lmp->dir)+1 : 0;
801 len += lmp->fname ? strlen(lmp->fname) : 0;
803 if(len+3 > sizeof(buf)){
804 space = len+3;
805 bfp = malloc(space*sizeof(char));
806 if(bfp == NULL){
807 emlwwrite(_("Can't malloc space for filename"),
808 NULL);
809 continue;
812 else{
813 bfp = buf;
814 space = sizeof(buf);
817 if(lmp->dir && lmp->dir[0])
818 snprintf(bfp, space, "%s%c%s", lmp->dir, C_FILESEP,
819 lmp->fname ? lmp->fname : "");
820 else
821 snprintf(bfp, space, "%s", lmp->fname ? lmp->fname : "");
823 (void) QuoteAttach(bfp, space);
825 (void) AppendAttachment(bfp, lm->size, cmt);
827 if(bfp != buf)
828 free(bfp);
831 zotlmlist(lm);
834 km_popped = saved_km_popped;
835 sgarbk = 1; /* clean up prompt */
837 break;
838 #endif
840 case (CTRL|'I') : /* tab */
841 if(headents[ods.cur_e].nickcmpl != NULL){
842 char *new_nickname = NULL;
843 UCS *strng;
844 UCS *start = NULL, *end = NULL, *before_start = NULL;
845 UCS *uprefix = NULL, *up1, *up2;
846 char *prefix = NULL, *saveprefix = NULL, *insert = NULL;
847 char *ambig = NULL;
848 int offset, prefixlen, add_a_comma = 0;
849 size_t l, l1, l2;
850 int ambiguity, fallthru = 1;
852 strng = ods.cur_l->text;
853 offset = HeaderOffset(ods.cur_e);
855 if(ods.p_ind > 0
856 && (start = &strng[ods.p_ind-1])
857 && (!*(start+1)
858 || ucs4_isspace(*(start+1))
859 || *(start+1) == ',')
860 && (*start
861 && !ucs4_isspace(*start)
862 && *start != ',')){
864 end = start+1;
866 while(start > strng
867 && *(start-1)
868 && *(start-1) != ',')
869 start--;
871 while(ucs4_isspace(*start))
872 start++;
874 if(*end != ',' && ods.cur_l->next)
875 add_a_comma++;
878 * Nickname prefix begins with start and ends
879 * with end-1. Replace those characters with
880 * completed nickname.
882 prefixlen = end-start;
883 uprefix = (UCS *) fs_get((prefixlen+1) * sizeof(UCS));
884 ucs4_strncpy(uprefix, start, prefixlen);
885 uprefix[prefixlen] = '\0';
886 prefix = ucs4_to_utf8_cpystr(uprefix);
887 fs_give((void **) &uprefix);
890 * Ambiguity == 0 means no matches exist.
891 * 1 means it is ambiguous and the longest
892 * unambiguous prefix beginning with prefix
893 * is in new_nickname.
894 * 2 means it is unambiguous and the answer
895 * is in new_nickname
897 * The expansion from == 2 doesn't have to have prefix
898 * as a prefix. For example, if the prefix is a prefix
899 * of the full name or a prefix of a nickname the answer
900 * might be the full address instead of the expanded
901 * prefix. In fact, that's probably the expected thing.
903 * Due to the way the build_abook_tries sets up the tries
904 * it is unfortunately the case that the expanded address
905 * is not entered in any trie, so after you get an
906 * unambiguous expansion if you try TAB again at that
907 * point you'll probably get a no match returned instead
908 * of an unambiguous. So be aware of that and handle
909 * that case ok by falling through to header_downline.
911 ambiguity = (*(headents[ods.cur_e].nickcmpl))(prefix,
912 &new_nickname, (lastch == ch), 0);
915 * Don't fall through on no-match TAB unless
916 * it is the second TAB.
918 if(ambiguity == 0 && lastch != ch){
919 lastch = 0;
920 break;
923 if(ambiguity != 1)
924 lastch = 0;
926 if(ambiguity == 0)
927 goto nomore_to_complete;
929 if(new_nickname){
930 if(strcmp(new_nickname, prefix)){
932 * We're trying to work with the way
933 * FormatLines works. It inserts text at the
934 * beginning of the line we pass in.
935 * So, remove the beginning of the line and
936 * have FormatLines put it back.
939 /* save part before start */
940 fallthru = 0;
941 before_start = strng;
942 uprefix = (UCS *) fs_get((start-before_start+1) * sizeof(UCS));
943 ucs4_strncpy(uprefix, before_start, start-before_start);
944 uprefix[start-before_start] = '\0';
945 saveprefix = ucs4_to_utf8_cpystr(uprefix);
947 /* figure out new cursor offset */
948 up1 = utf8_to_ucs4_cpystr(new_nickname);
949 if(up1){
950 offset += (ucs4_strlen(up1) - prefixlen);
951 fs_give((void **) &up1);
954 fs_give((void **) &uprefix);
957 * Delete everything up to end by
958 * copying characters to start of buffer.
960 up1 = before_start;
961 up2 = end;
962 for(i = ods.p_len - (end - before_start) + 1; i > 0; i--)
963 *up1++ = *up2++;
965 ods.p_len -= (end - before_start);
967 if(saveprefix){
968 l1 = strlen(saveprefix);
969 l2 = strlen(new_nickname);
970 l = l1 + l2;
972 /* add a comma? */
973 if(add_a_comma && ambiguity == 2){
974 l++;
975 offset++;
977 else
978 add_a_comma = 0;
980 insert = (char *) fs_get((l+1) * sizeof(char));
983 * Insert is the before start stuff plus the
984 * new nickname, and we're going to let
985 * FormatLines put it together for us.
987 if(insert){
988 strncpy(insert, saveprefix, l);
989 strncpy(insert+l1, new_nickname, l-l1);
990 if(add_a_comma)
991 insert[l-1] = ',';
993 insert[l] = '\0';
996 fs_give((void **) &saveprefix);
1000 if(insert && FormatLines(ods.cur_l, insert,
1001 term.t_ncol - headents[ods.cur_e].prwid,
1002 headents[ods.cur_e].break_on_comma,0)==-1){
1003 emlwwrite(_("Format lines failed!"), NULL);
1006 if(insert)
1007 fs_give((void **) &insert);
1009 HeaderFocus(ods.cur_e, offset);
1011 else{
1012 (*term.t_beep)();
1015 ambig = new_nickname;
1016 new_nickname = NULL;
1019 if(!ambig && prefix){
1020 ambig = prefix;
1021 prefix = NULL;
1025 if(ambiguity == 2 && fallthru){
1026 if(prefix)
1027 fs_give((void **) &prefix);
1029 if(new_nickname)
1030 fs_give((void **) &new_nickname);
1032 if(ambig)
1033 fs_give((void **) &ambig);
1035 UpdateHeader(0);
1036 PaintBody(0);
1037 goto nomore_to_complete;
1040 UpdateHeader(0);
1041 PaintBody(0);
1043 if(prefix)
1044 fs_give((void **) &prefix);
1046 if(new_nickname)
1047 fs_give((void **) &new_nickname);
1049 if(ambig)
1050 fs_give((void **) &ambig);
1052 else{
1053 goto nomore_to_complete;
1056 break;
1058 else{
1059 nomore_to_complete:
1060 ods.p_ind = 0; /* fall through... */
1063 case (CTRL|'N') :
1064 case KEY_DOWN :
1065 header_downline(!hdr_only, hdr_only);
1066 break;
1068 case (CTRL|'P') :
1069 case KEY_UP :
1070 header_upline(1);
1071 break;
1073 case (CTRL|'V') : /* down a page */
1074 case KEY_PGDN :
1075 cur_e = ods.cur_e;
1076 if(!next_sel_hline(&cur_e, ods.cur_l)){
1077 header_downline(!hdr_only, hdr_only);
1078 if(!(gmode & MDHDRONLY))
1079 retval = -1; /* tell caller we fell out */
1081 else{
1082 int move_down, bot_pline;
1083 struct hdr_line *new_cur_l, *line, *next_line, *prev_line;
1085 move_down = BOTTOM() - 2 - ods.p_line;
1086 if(move_down < 0)
1087 move_down = 0;
1090 * Count down move_down lines to find the pointer to the line
1091 * that we want to become the current line.
1093 new_cur_l = ods.cur_l;
1094 cur_e = ods.cur_e;
1095 for(i = 0; i < move_down; i++){
1096 next_line = next_hline(&cur_e, new_cur_l);
1097 if(!next_line)
1098 break;
1100 new_cur_l = next_line;
1103 if(headents[cur_e].blank){
1104 next_line = next_sel_hline(&cur_e, new_cur_l);
1105 if(!next_line)
1106 break;
1108 new_cur_l = next_line;
1112 * Now call header_downline until we get down to the
1113 * new current line, so that the builders all get called.
1114 * New_cur_l will remain valid since we won't be calling
1115 * a builder for it during this loop.
1117 while(ods.cur_l != new_cur_l && header_downline(0, 0))
1121 * Count back up, if we're at the bottom, to find the new
1122 * top line.
1124 cur_e = ods.cur_e;
1125 if(next_hline(&cur_e, ods.cur_l) == NULL){
1127 * Cursor stops at bottom of headers, which is where
1128 * we are right now. Put as much of headers on
1129 * screen as will fit. Count up to figure
1130 * out which line is top_l and which p_line cursor is on.
1132 cur_e = ods.cur_e;
1133 line = ods.cur_l;
1134 /* leave delimiter on screen, too */
1135 bot_pline = BOTTOM() - 1 - ((gmode & MDHDRONLY) ? 0 : 1);
1136 for(i = COMPOSER_TOP_LINE; i < bot_pline; i++){
1137 prev_line = prev_hline(&cur_e, line);
1138 if(!prev_line)
1139 break;
1141 line = prev_line;
1144 ods.top_l = line;
1145 ods.top_e = cur_e;
1146 ods.p_line = i;
1149 else{
1150 ods.top_l = ods.cur_l;
1151 ods.top_e = ods.cur_e;
1153 * We don't want to scroll down further than the
1154 * delimiter, so check to see if that is the case.
1155 * If it is, we move the p_line down the screen
1156 * until the bottom line is where we want it.
1158 bot_pline = BOTTOM() - 1 - ((gmode & MDHDRONLY) ? 0 : 1);
1159 cur_e = ods.cur_e;
1160 line = ods.cur_l;
1161 for(i = bot_pline; i > COMPOSER_TOP_LINE; i--){
1162 next_line = next_hline(&cur_e, line);
1163 if(!next_line)
1164 break;
1166 line = next_line;
1170 * i is the desired value of p_line.
1171 * If it is greater than COMPOSER_TOP_LINE, then
1172 * we need to adjust top_l.
1174 ods.p_line = i;
1175 line = ods.top_l;
1176 cur_e = ods.top_e;
1177 for(; i > COMPOSER_TOP_LINE; i--){
1178 prev_line = prev_hline(&cur_e, line);
1179 if(!prev_line)
1180 break;
1182 line = prev_line;
1185 ods.top_l = line;
1186 ods.top_e = cur_e;
1189 * Special case. If p_line is within one of the bottom,
1190 * move it to the bottom.
1192 if(ods.p_line == bot_pline - 1){
1193 header_downline(0, 0);
1194 /* but put these back where we want them */
1195 ods.p_line = bot_pline;
1196 ods.top_l = line;
1197 ods.top_e = cur_e;
1201 UpdateHeader(0);
1202 PaintHeader(COMPOSER_TOP_LINE, FALSE);
1203 PaintBody(1);
1206 break;
1208 case (CTRL|'Y') : /* up a page */
1209 case KEY_PGUP :
1210 for(i = 0; header_upline(0) && i <= FULL_SCR(); i++)
1211 if(i < 0)
1212 break;
1214 break;
1216 #ifdef MOUSE
1217 case KEY_MOUSE:
1218 mouse_get_last (NULL, &mp);
1219 switch(mp.button){
1220 case M_BUTTON_LEFT :
1221 if (!mp.doubleclick) {
1222 if (mp.row < ods.p_line) {
1223 for (i = ods.p_line - mp.row;
1224 i > 0 && header_upline(0);
1225 --i)
1228 else {
1229 for (i = mp.row-ods.p_line;
1230 i > 0 && header_downline(!hdr_only, 0);
1231 --i)
1235 if((ods.p_ind = mp.col - headents[ods.cur_e].prwid) <= 0)
1236 ods.p_ind = 0;
1238 /* -3 is returned if we drop out bottom
1239 * *and* want to process a mousepress. The Headereditor
1240 * wrapper should make sense of this return code.
1242 if (ods.p_line >= ComposerTopLine)
1243 retval = -3;
1246 break;
1248 case M_BUTTON_RIGHT :
1249 #ifdef _WINDOWS
1250 pico_popup();
1251 #endif
1252 case M_BUTTON_MIDDLE :
1253 default : /* NOOP */
1254 break;
1257 break;
1258 #endif /* MOUSE */
1260 case (CTRL|'T') : /* Call field selector */
1261 errmss = NULL;
1262 if(headents[ods.cur_e].is_attach) {
1263 /*--- selector for attachments ----*/
1264 char dir[NLINE], fn[NLINE], sz[NLINE];
1265 LMLIST *lm = NULL, *lmp;
1267 strncpy(dir, (gmode & MDCURDIR)
1268 ? (browse_dir[0] ? browse_dir : ".")
1269 : ((gmode & MDTREE) || opertree[0])
1270 ? opertree
1271 : (browse_dir[0] ? browse_dir
1272 : gethomedir(NULL)), sizeof(dir));
1273 dir[sizeof(dir)-1] = '\0';
1274 fn[0] = sz[0] = '\0';
1275 if(FileBrowse(dir, sizeof(dir), fn, sizeof(fn), sz, sizeof(sz),
1276 FB_READ | FB_ATTACH | FB_LMODEPOS, &lm) == 1){
1277 char buf[NLINE], *bfp;
1278 size_t len;
1280 for(lmp = lm; lmp; lmp = lmp->next){
1281 size_t space;
1283 len = lmp->dir ? strlen(lmp->dir)+1 : 0;
1284 len += lmp->fname ? strlen(lmp->fname) : 0;
1285 len += 7;
1286 len += strlen(lmp->size);
1288 if(len+3 > sizeof(buf)){
1289 space = len+3;
1290 bfp = malloc(space*sizeof(char));
1291 if(bfp == NULL){
1292 emlwwrite(_("Can't malloc space for filename"),
1293 NULL);
1294 continue;
1297 else{
1298 bfp = buf;
1299 space = sizeof(buf);
1302 if(lmp->dir && lmp->dir[0])
1303 snprintf(bfp, space, "%s%c%s", lmp->dir, C_FILESEP,
1304 lmp->fname ? lmp->fname : "");
1305 else
1306 snprintf(bfp, space, "%s", lmp->fname ? lmp->fname : "");
1308 (void) QuoteAttach(bfp, space);
1310 snprintf(bfp + strlen(bfp), space-strlen(bfp), " (%s) \"\"%s", lmp->size,
1311 (!headents[ods.cur_e].hd_text->text[0]) ? "":",");
1313 if(FormatLines(headents[ods.cur_e].hd_text, bfp,
1314 term.t_ncol - headents[ods.cur_e].prwid,
1315 headents[ods.cur_e].break_on_comma,0)==-1){
1316 emlwwrite(_("Format lines failed!"), NULL);
1319 if(bfp != buf)
1320 free(bfp);
1322 UpdateHeader(0);
1325 zotlmlist(lm);
1326 } /* else, nothing of interest */
1327 } else if (headents[ods.cur_e].selector != NULL) {
1328 VARS_TO_SAVE *saved_state;
1330 /*---- General selector for non-attachments -----*/
1333 * Since the selector may make a new call back to pico()
1334 * we need to save and restore the pico state here.
1336 if((saved_state = save_pico_state()) != NULL){
1337 bufp = (*(headents[ods.cur_e].selector))(&errmss);
1338 restore_pico_state(saved_state);
1339 free_pico_state(saved_state);
1340 ttresize(); /* fixup screen bufs */
1341 picosigs(); /* restore altered signals */
1343 else{
1344 char *s = "Not enough memory";
1345 size_t len;
1347 len = strlen(s);
1348 errmss = (char *)malloc((len+1) * sizeof(char));
1349 strncpy(errmss, s, len+1);
1350 errmss[len] = '\0';
1351 bufp = NULL;
1354 if(bufp != NULL) {
1355 mangled = 0;
1356 err = NULL;
1357 if(headents[ods.cur_e].break_on_comma) {
1358 /*--- Must be an address ---*/
1361 * If current line is empty and there are more
1362 * lines that follow, delete the empty lines
1363 * before adding the new address.
1365 if(ods.cur_l->text[0] == '\0' && ods.cur_l->next){
1366 do {
1367 KillHeaderLine(ods.cur_l, 1);
1368 ods.p_len = ucs4_strlen(ods.cur_l->text);
1369 } while(ods.cur_l->text[0] == '\0' && ods.cur_l->next);
1372 ods.p_ind = 0;
1374 if(ods.cur_l->text[0] != '\0'){
1375 struct hdr_line *h, *start_of_addr;
1376 int q = 0;
1378 /* cur is not first line */
1379 if(ods.cur_l != headents[ods.cur_e].hd_text){
1381 * Protect against adding a new entry into
1382 * the middle of a long, continued entry.
1384 start_of_addr = NULL; /* cur_l is a good place to be */
1385 q = 0;
1386 for(h = headents[ods.cur_e].hd_text; h; h = h->next){
1387 if(ucs4_strqchr(h->text, ',', &q, -1)){
1388 start_of_addr = NULL;
1389 q = 0;
1391 else if(start_of_addr == NULL)
1392 start_of_addr = h;
1394 if(h->next == ods.cur_l)
1395 break;
1398 if(start_of_addr){
1399 ods.cur_l = start_of_addr;
1400 ods.p_len = ucs4_strlen(ods.cur_l->text);
1404 for(i = ++ods.p_len; i; i--)
1405 ods.cur_l->text[i] = ods.cur_l->text[i-1];
1407 ods.cur_l->text[0] = ',';
1410 if(FormatLines(ods.cur_l, bufp,
1411 (term.t_ncol-headents[ods.cur_e].prwid),
1412 headents[ods.cur_e].break_on_comma, 0) == -1){
1413 emlwrite("Problem adding address to header !",
1414 NULL);
1415 (*term.t_beep)();
1416 break;
1420 * If the "selector" has a "builder" as well, pass
1421 * what was just selected thru the builder...
1423 if(headents[ods.cur_e].builder){
1424 struct hdr_line *l;
1425 int cur_row, top_too = 0;
1427 for(l = headents[ods.cur_e].hd_text, cur_row = 0;
1428 l && l != ods.cur_l;
1429 l = l->next, cur_row++)
1432 top_too = headents[ods.cur_e].hd_text == ods.top_l;
1434 if(call_builder(&headents[ods.cur_e], &mangled,
1435 &err) < 0){
1436 fix_mangle_and_err(&mangled, &err,
1437 headents[ods.cur_e].name);
1440 for(ods.cur_l = headents[ods.cur_e].hd_text;
1441 ods.cur_l->next && cur_row;
1442 ods.cur_l = ods.cur_l->next, cur_row--)
1445 if(top_too)
1446 ods.top_l = headents[ods.cur_e].hd_text;
1449 UpdateHeader(0);
1450 } else {
1451 UCS *u;
1453 u = utf8_to_ucs4_cpystr(bufp);
1454 if(u){
1455 ucs4_strncpy(headents[ods.cur_e].hd_text->text, u, HLSZ);
1456 headents[ods.cur_e].hd_text->text[HLSZ-1] = '\0';
1457 fs_give((void **) &u);
1461 free(bufp);
1462 /* mark this entry dirty */
1463 mark_sticky(&headents[ods.cur_e]);
1464 headents[ods.cur_e].dirty = 1;
1465 fix_mangle_and_err(&mangled,&err,headents[ods.cur_e].name);
1467 } else {
1468 /*----- No selector -----*/
1469 (*term.t_beep)();
1470 continue;
1473 PaintBody(0);
1474 if(errmss != NULL) {
1475 (*term.t_beep)();
1476 emlwrite(errmss, NULL);
1477 free(errmss);
1478 errmss = NULL;
1480 continue;
1482 case (CTRL|'G'): /* HELP */
1483 if(term.t_mrow == 0){
1484 if(km_popped == 0){
1485 km_popped = 2;
1486 sgarbk = 1; /* bring up menu */
1487 break;
1491 if(!ComposerHelp(ods.cur_e))
1492 break; /* else, fall through... */
1494 case (CTRL|'L'): /* redraw requested */
1495 PaintBody(0);
1496 break;
1498 case (CTRL|'_'): /* file editor */
1499 if(headents[ods.cur_e].fileedit != NULL){
1500 struct headerentry *e;
1501 struct hdr_line *line;
1502 int sz = 0;
1503 char *filename = NULL;
1504 VARS_TO_SAVE *saved_state;
1507 * Since the fileedit will make a new call back to pico()
1508 * we need to save and restore the pico state here.
1510 if((saved_state = save_pico_state()) != NULL){
1511 UCS *u;
1513 e = &headents[ods.cur_e];
1515 for(line = e->hd_text; line != NULL; line = line->next)
1516 sz += ucs4_strlen(line->text);
1518 u = (UCS *)malloc((sz+1) * sizeof(*u));
1519 if(u){
1520 u[0] = '\0';
1521 for(line = e->hd_text; line != NULL; line = line->next)
1522 ucs4_strncat(u, line->text, sz+1-ucs4_strlen(u)-1);
1524 filename = ucs4_to_utf8_cpystr(u);
1525 free(u);
1528 errmss = (*(headents[ods.cur_e].fileedit))(filename);
1530 if(filename)
1531 free(filename);
1533 restore_pico_state(saved_state);
1534 free_pico_state(saved_state);
1535 ttresize(); /* fixup screen bufs */
1536 picosigs(); /* restore altered signals */
1538 else{
1539 char *s = "Not enough memory";
1540 size_t len;
1542 len = strlen(s);
1543 errmss = (char *)malloc((len+1) * sizeof(char));
1544 strncpy(errmss, s, len+1);
1545 errmss[len] = '\0';
1548 PaintBody(0);
1550 if(errmss != NULL) {
1551 (*term.t_beep)();
1552 emlwrite(errmss, NULL);
1553 free(errmss);
1554 errmss = NULL;
1557 continue;
1559 else
1560 goto bleep;
1562 break;
1564 default : /* huh? */
1565 bleep:
1566 unknown_command(ch);
1568 case NODATA:
1569 break;
1572 while (ods.p_line < ComposerTopLine);
1574 display_delimiter(1);
1575 curwp->w_flag |= WFMODE;
1576 movecursor(currow, curcol);
1577 ComposerEditing = FALSE;
1578 if (ComposerTopLine == BOTTOM()){
1579 UpdateHeader(0);
1580 PaintHeader(COMPOSER_TOP_LINE, FALSE);
1581 PaintBody(1);
1584 return(retval);
1592 header_downline(int beyond, int gripe)
1594 struct hdr_line *new_l, *l;
1595 int new_e, status, fullpaint, len, e, incr = 0;
1597 /* calculate the next line: physical *and* logical */
1598 status = 0;
1599 new_e = ods.cur_e;
1600 if((new_l = next_sel_hline(&new_e, ods.cur_l)) == NULL && !beyond){
1602 if(gripe){
1603 char xx[81];
1605 strncpy(xx, "Can't move down. Use ^X to ", sizeof(xx));
1606 xx[sizeof(xx)-1] = '\0';
1607 strncat(xx, (Pmaster && Pmaster->exit_label)
1608 ? Pmaster->exit_label
1609 : (gmode & MDHDRONLY)
1610 ? "eXit/Save"
1611 : (gmode & MDVIEW)
1612 ? "eXit"
1613 : "Send", sizeof(xx)-strlen(xx)-1);
1614 xx[sizeof(xx)-1] = '\0';
1615 strncat(xx, ".", sizeof(xx)-strlen(xx)-1);
1616 xx[sizeof(xx)-1] = '\0';
1617 emlwrite(xx, NULL);
1620 return(0);
1624 * Because of blank header lines the cursor may need to move down
1625 * more than one line. Figure out how far.
1627 e = ods.cur_e;
1628 l = ods.cur_l;
1629 while(l != new_l){
1630 if((l = next_hline(&e, l)) != NULL)
1631 incr++;
1632 else
1633 break; /* can't happen */
1636 ods.p_line += incr;
1637 fullpaint = ods.p_line >= BOTTOM(); /* force full redraw? */
1639 /* expand what needs expanding */
1640 if(new_e != ods.cur_e || !new_l){ /* new (or last) field ! */
1641 if(new_l)
1642 InvertPrompt(ods.cur_e, FALSE); /* turn off current entry */
1644 if(headents[ods.cur_e].is_attach) { /* verify data ? */
1645 if((status = FormatSyncAttach()) != 0){ /* fixup if 1 or -1 */
1646 headents[ods.cur_e].rich_header = 0;
1647 if(FormatLines(headents[ods.cur_e].hd_text, "",
1648 term.t_ncol-headents[new_e].prwid,
1649 headents[ods.cur_e].break_on_comma, 0) == -1)
1650 emlwwrite(_("Format lines failed!"), NULL);
1652 } else if(headents[ods.cur_e].builder) { /* expand addresses */
1653 int mangled = 0;
1654 char *err = NULL;
1656 if((status = call_builder(&headents[ods.cur_e], &mangled, &err))>0){
1657 struct hdr_line *l; /* fixup ods.cur_l */
1658 ods.p_line = 0; /* force top line recalc */
1659 for(l = headents[ods.cur_e].hd_text; l; l = l->next)
1660 ods.cur_l = l;
1662 if(new_l) /* if new_l, force validity */
1663 new_l = headents[new_e].hd_text;
1665 NewTop(0); /* get new top_l */
1667 else if(status < 0){ /* bad addr? no leave! */
1668 --ods.p_line;
1669 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
1670 InvertPrompt(ods.cur_e, TRUE);
1671 return(0);
1674 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
1677 if(new_l){ /* if one below, turn it on */
1678 InvertPrompt(new_e, TRUE);
1679 sgarbk = 1; /* paint keymenu too */
1683 if(new_l){ /* fixup new pointers */
1684 ods.cur_l = (ods.cur_e != new_e) ? headents[new_e].hd_text : new_l;
1685 ods.cur_e = new_e;
1686 if(ods.p_ind > (len = ucs4_strlen(ods.cur_l->text)))
1687 ods.p_ind = len;
1690 if(!new_l || status || fullpaint){ /* handle big screen paint */
1691 UpdateHeader(0);
1692 PaintHeader(COMPOSER_TOP_LINE, FALSE);
1693 PaintBody(1);
1695 if(!new_l){ /* make sure we're done */
1696 ods.p_line = ComposerTopLine;
1697 InvertPrompt(ods.cur_e, FALSE); /* turn off current entry */
1701 return(new_l ? 1 : 0);
1709 header_upline(int gripe)
1711 struct hdr_line *new_l, *l;
1712 int new_e, status, fullpaint, len, e, incr = 0;
1713 EML eml;
1715 /* calculate the next line: physical *and* logical */
1716 status = 0;
1717 new_e = ods.cur_e;
1718 if(!(new_l = prev_sel_hline(&new_e, ods.cur_l))){ /* all the way up! */
1719 ods.p_line = COMPOSER_TOP_LINE;
1720 if(gripe){
1721 eml.s = (Pmaster->pine_flags & MDHDRONLY) ? "entry" : "header";
1722 emlwrite(_("Can't move beyond top of %s"), &eml);
1725 return(0);
1729 * Because of blank header lines the cursor may need to move up
1730 * more than one line. Figure out how far.
1732 e = ods.cur_e;
1733 l = ods.cur_l;
1734 while(l != new_l){
1735 if((l = prev_hline(&e, l)) != NULL)
1736 incr++;
1737 else
1738 break; /* can't happen */
1741 ods.p_line -= incr;
1742 fullpaint = ods.p_line <= COMPOSER_TOP_LINE;
1744 if(new_e != ods.cur_e){ /* new field ! */
1745 InvertPrompt(ods.cur_e, FALSE);
1746 if(headents[ods.cur_e].is_attach){
1747 if((status = FormatSyncAttach()) != 0){ /* non-zero ? reformat field */
1748 headents[ods.cur_e].rich_header = 0;
1749 if(FormatLines(headents[ods.cur_e].hd_text, "",
1750 term.t_ncol - headents[ods.cur_e].prwid,
1751 headents[ods.cur_e].break_on_comma,0) == -1)
1752 emlwwrite(_("Format lines failed!"), NULL);
1755 else if(headents[ods.cur_e].builder){
1756 int mangled = 0;
1757 char *err = NULL;
1759 if((status = call_builder(&headents[ods.cur_e], &mangled,
1760 &err)) >= 0){
1761 /* repair new_l */
1762 for(new_l = headents[new_e].hd_text;
1763 new_l->next;
1764 new_l=new_l->next)
1767 /* and cur_l (required in fix_and... */
1768 ods.cur_l = new_l;
1770 else{
1771 ++ods.p_line;
1772 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
1773 InvertPrompt(ods.cur_e, TRUE);
1774 return(0);
1777 fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name);
1780 InvertPrompt(new_e, TRUE);
1781 sgarbk = 1;
1784 ods.cur_e = new_e; /* update pointers */
1785 ods.cur_l = new_l;
1786 if(ods.p_ind > (len = ucs4_strlen(ods.cur_l->text)))
1787 ods.p_ind = len;
1789 if(status > 0 || fullpaint){
1790 UpdateHeader(0);
1791 PaintHeader(COMPOSER_TOP_LINE, FALSE);
1792 PaintBody(1);
1795 return(1);
1803 AppendAttachment(char *fn, char *sz, char *cmt)
1805 int a_e, status, spaces;
1806 struct hdr_line *lp;
1807 char b[256];
1808 UCS *u;
1810 /*--- Find headerentry that is attachments (only first) --*/
1811 for(a_e = 0; headents[a_e].name != NULL; a_e++ )
1812 if(headents[a_e].is_attach){
1813 /* make sure field stays displayed */
1814 headents[a_e].rich_header = 0;
1815 headents[a_e].display_it = 1;
1816 break;
1819 /* append new attachment line */
1820 for(lp = headents[a_e].hd_text; lp->next; lp=lp->next)
1823 /* build new attachment line */
1824 if(lp->text[0]){ /* adding a line? */
1825 UCS comma[2];
1827 comma[0] = ',';
1828 comma[1] = '\0';
1829 ucs4_strncat(lp->text, comma, HLSZ-ucs4_strlen(lp->text)-1); /* append delimiter */
1830 if((lp->next = HALLOC()) != NULL){ /* allocate new line */
1831 lp->next->prev = lp;
1832 lp->next->next = NULL;
1833 lp = lp->next;
1835 else{
1836 emlwwrite(_("Can't allocate line for new attachment!"), NULL);
1837 return(0);
1842 spaces = (*fn == '\"') ? 0 : (strpbrk(fn, "(), \t") != NULL);
1843 snprintf(b, sizeof(b), "%s%s%s (%s) \"%.*s\"",
1844 spaces ? "\"" : "", fn, spaces ? "\"" : "",
1845 sz ? sz : "", 80, cmt ? cmt : "");
1846 u = utf8_to_ucs4_cpystr(b);
1847 if(u){
1848 ucs4_strncpy(lp->text, u, HLSZ);
1849 lp->text[HLSZ-1] = '\0';
1850 fs_give((void **) &u);
1853 /* validate the new attachment, and reformat if needed */
1854 if((status = SyncAttach()) != 0){
1855 EML eml;
1857 if(status < 0){
1858 eml.s = fn;
1859 emlwwrite(_("Problem attaching: %s"), &eml);
1862 if(FormatLines(headents[a_e].hd_text, "",
1863 term.t_ncol - headents[a_e].prwid,
1864 headents[a_e].break_on_comma, 0) == -1){
1865 emlwwrite(_("Format lines failed!"), NULL);
1866 return(0);
1870 UpdateHeader(0);
1871 PaintHeader(COMPOSER_TOP_LINE, status != 0);
1872 PaintBody(1);
1873 return(status != 0);
1878 * LineEdit - Always use insert mode and handle line wrapping
1880 * returns:
1881 * Any characters typed in that aren't printable
1882 * (i.e. commands)
1884 * notes:
1885 * Assume we are guaranteed that there is sufficiently
1886 * more buffer space in a line than screen width (just one
1887 * less thing to worry about). If you want to change this,
1888 * then pputc will have to be taught to check the line buffer
1889 * length, and HALLOC() will probably have to become a func.
1892 LineEdit(int allowedit, UCS *lastch)
1894 register struct hdr_line *lp; /* temporary line pointer */
1895 register int i;
1896 UCS ch = 0;
1897 register int status; /* various func's return val */
1898 UCS *tbufp; /* temporary buffer pointers */
1899 int skipmove = 0;
1900 UCS *strng;
1901 UCS last_key; /* last keystroke */
1903 strng = ods.cur_l->text; /* initialize offsets */
1904 ods.p_len = MIN(ucs4_strlen(strng), HLSZ);
1905 if(ods.p_ind < 0) /* offset within range? */
1906 ods.p_ind = 0;
1907 else if(ods.p_ind > ods.p_len)
1908 ods.p_ind = ods.p_len;
1909 else if(ucs4_str_width_ptr_to_ptr(&strng[0], &strng[ods.p_ind]) > LINEWID()){
1910 UCS *u;
1912 u = ucs4_particular_width(strng, LINEWID());
1913 ods.p_ind = u - strng;
1916 while(1){ /* edit the line... */
1918 if(Pmaster && subject_line != NULL
1919 && ods.cur_l == subject_line
1920 && ods.cur_l->text[0] == 0)
1921 (*Pmaster->newthread)();
1923 if(skipmove)
1924 skipmove = 0;
1925 else
1926 HeaderPaintCursor();
1928 last_key = ch;
1929 if(ch && lastch)
1930 *lastch = ch;
1932 (*term.t_flush)(); /* get everything out */
1934 #ifdef MOUSE
1935 mouse_in_content(KEY_MOUSE, -1, -1, 0, 0);
1936 register_mfunc(mouse_in_content,2,0,term.t_nrow-(term.t_mrow+1),
1937 term.t_ncol);
1938 #endif
1939 #ifdef _WINDOWS
1940 mswin_setdndcallback (composer_file_drop);
1941 mswin_mousetrackcallback(pico_cursor);
1942 #endif
1944 ch = GetKey();
1946 if (term.t_nrow < 6 && ch != NODATA){
1947 (*term.t_beep)();
1948 emlwrite(_("Please make the screen larger."), NULL);
1949 continue;
1952 #ifdef MOUSE
1953 clear_mfunc(mouse_in_content);
1954 #endif
1955 #ifdef _WINDOWS
1956 mswin_cleardndcallback ();
1957 mswin_mousetrackcallback(NULL);
1958 #endif
1960 switch(ch){
1961 case DEL :
1962 if(gmode & P_DELRUBS)
1963 ch = KEY_DEL;
1965 default :
1966 (*Pmaster->keybinput)();
1967 if(!time_to_check())
1968 break;
1970 case NODATA : /* new mail ? */
1971 if((*Pmaster->newmail)(ch == NODATA ? 0 : 2, 1) >= 0){
1972 int rv;
1974 if(km_popped){
1975 term.t_mrow = 2;
1976 curwp->w_ntrows -= 2;
1979 clearcursor();
1980 mlerase();
1981 rv = (*Pmaster->showmsg)(ch);
1982 ttresize();
1983 picosigs();
1984 if(rv) /* Did showmsg corrupt the display? */
1985 PaintBody(0); /* Yes, repaint */
1987 mpresf = 1;
1988 if(km_popped){
1989 term.t_mrow = 0;
1990 curwp->w_ntrows += 2;
1994 clearcursor();
1995 movecursor(ods.p_line,
1996 ucs4_str_width_ptr_to_ptr(strng, &strng[ods.p_ind])+headents[ods.cur_e].prwid);
1997 if(ch == NODATA) /* GetKey timed out */
1998 continue;
2000 break;
2003 if(mpresf){ /* blast old messages */
2004 if(mpresf++ > NMMESSDELAY){ /* every few keystrokes */
2005 mlerase();
2006 movecursor(ods.p_line,
2007 ucs4_str_width_ptr_to_ptr(strng, &strng[ods.p_ind])+headents[ods.cur_e].prwid);
2011 tbufp = &strng[ods.p_len];
2013 if(VALID_KEY(ch)){ /* char input */
2015 * if we are allowing editing, insert the new char
2016 * end up leaving tbufp pointing to newly
2017 * inserted character in string, and offset to the
2018 * index of the character after the inserted ch ...
2020 if(allowedit){
2021 if(headents[ods.cur_e].is_attach && intag(strng,ods.p_ind)){
2022 emlwwrite(_("Can't edit attachment number!"), NULL);
2023 continue;
2026 if(headents[ods.cur_e].single_space){
2027 if(ch == ' '
2028 && (strng[ods.p_ind]==' ' || strng[ods.p_ind-1]==' '))
2029 continue;
2033 * go ahead and add the character...
2035 if(ods.p_len < HLSZ){
2036 tbufp = &strng[++ods.p_len]; /* find the end */
2038 *tbufp = tbufp[-1];
2039 } while(--tbufp > &strng[ods.p_ind]); /* shift right */
2040 strng[ods.p_ind++] = ch; /* add char to str */
2043 /* mark this entry dirty */
2044 mark_sticky(&headents[ods.cur_e]);
2045 headents[ods.cur_e].dirty = 1;
2048 * then find out where things fit...
2050 if(ucs4_str_width_ptr_to_ptr(&strng[0], &strng[ods.p_len]) < LINEWID()){
2051 CELL c;
2053 c.c = ch & CELLMASK;
2054 c.a = 0;
2055 if(pinsert(c)){ /* add char to str */
2056 skipmove++; /* must'a been optimal */
2057 continue; /* on to the next! */
2060 else{
2061 if((status = FormatLines(ods.cur_l, "", LINEWID(),
2062 headents[ods.cur_e].break_on_comma,0)) == -1){
2063 (*term.t_beep)();
2064 continue;
2066 else{
2068 * during the format, the dot may have moved
2069 * down to the next line...
2071 if(ods.p_ind >= ucs4_strlen(strng)){
2072 ods.p_line++;
2073 ods.p_ind -= ucs4_strlen(strng);
2074 ods.cur_l = ods.cur_l->next;
2075 strng = ods.cur_l->text;
2078 ods.p_len = ucs4_strlen(strng);
2081 UpdateHeader(0);
2082 PaintHeader(COMPOSER_TOP_LINE, FALSE);
2083 PaintBody(1);
2084 continue;
2087 else{
2088 rdonly();
2089 continue;
2092 else { /* interpret ch as a command */
2093 switch (ch = normalize_cmd(ch, ckm, 2)) {
2094 case (CTRL|KEY_LEFT): /* word skip left */
2095 if(ods.p_ind > 0) /* Scoot one char left if possible */
2096 ods.p_ind--;
2098 if(ods.p_ind == 0)
2100 if(ods.p_line != COMPOSER_TOP_LINE)
2101 ods.p_ind = 1000; /* put cursor at end of line */
2102 return(KEY_UP);
2105 while(ods.p_ind > 0 && !ucs4_isalnum(strng[ods.p_ind]))
2106 ods.p_ind--; /* skip any whitespace we're in */
2108 while(ods.p_ind > 0) {
2109 /* Bail if the character right before this one is whitespace */
2110 if(ods.p_ind > 1 && !ucs4_isalnum(strng[ods.p_ind - 1]))
2111 break;
2112 ods.p_ind--;
2114 continue;
2116 case (CTRL|'@') : /* word skip */
2117 case (CTRL|KEY_RIGHT):
2118 while(ucs4_isalnum(strng[ods.p_ind]))
2119 ods.p_ind++; /* skip any text we're in */
2121 while(strng[ods.p_ind] && !ucs4_isalnum(strng[ods.p_ind]))
2122 ods.p_ind++; /* skip any whitespace after it */
2124 if(strng[ods.p_ind] == '\0'){
2125 ods.p_ind = 0; /* end of line, let caller handle it */
2126 return(KEY_DOWN);
2129 continue;
2131 case (CTRL|'K') : /* kill line cursor's on */
2132 if(!allowedit){
2133 rdonly();
2134 continue;
2137 lp = ods.cur_l;
2138 if (!(gmode & MDDTKILL))
2139 ods.p_ind = 0;
2141 if(KillHeaderLine(lp, (last_key == (CTRL|'K')))){
2142 if(TERM_OPTIMIZE &&
2143 !(ods.cur_l->prev==NULL && ods.cur_l->next==NULL))
2144 scrollup(wheadp, ods.p_line, 1);
2146 if(ods.cur_l->next == NULL)
2147 if(zotcomma(ods.cur_l->text)){
2148 if(ods.p_ind > 0)
2149 ods.p_ind = ucs4_strlen(ods.cur_l->text);
2152 i = (ods.p_line == COMPOSER_TOP_LINE);
2153 UpdateHeader(0);
2154 PaintHeader(COMPOSER_TOP_LINE, TRUE);
2156 if(km_popped){
2157 km_popped--;
2158 movecursor(term.t_nrow, 0);
2159 peeol();
2162 PaintBody(1);
2165 strng = ods.cur_l->text;
2166 ods.p_len = ucs4_strlen(strng);
2167 headents[ods.cur_e].sticky = 0;
2168 headents[ods.cur_e].dirty = 1;
2169 continue;
2171 case (CTRL|'U') : /* un-delete deleted lines */
2172 if(!allowedit){
2173 rdonly();
2174 continue;
2177 if(SaveHeaderLines()){
2178 UpdateHeader(0);
2179 PaintHeader(COMPOSER_TOP_LINE, FALSE);
2180 if(km_popped){
2181 km_popped--;
2182 movecursor(term.t_nrow, 0);
2183 peeol();
2186 PaintBody(1);
2187 strng = ods.cur_l->text;
2188 ods.p_len = ucs4_strlen(strng);
2189 mark_sticky(&headents[ods.cur_e]);
2190 headents[ods.cur_e].dirty = 1;
2192 else
2193 /* TRANSLATORS: Killing text is deleting it and
2194 Unkilling text is undeleting killed text. */
2195 emlwrite(_("Problem Unkilling text"), NULL);
2196 continue;
2198 case (CTRL|'F') :
2199 case KEY_RIGHT: /* move character right */
2200 if(ods.p_ind < ods.p_len){
2201 ods.p_ind++;
2202 continue;
2204 else if(gmode & MDHDRONLY)
2205 continue;
2207 ods.p_ind = 0;
2208 return(KEY_DOWN);
2210 case (CTRL|'B') :
2211 case KEY_LEFT : /* move character left */
2212 if(ods.p_ind > 0){
2213 ods.p_ind--;
2214 continue;
2216 if(ods.p_line != COMPOSER_TOP_LINE)
2217 ods.p_ind = 1000; /* put cursor at end of line */
2218 return(KEY_UP);
2220 case (CTRL|'M') : /* goto next field */
2221 ods.p_ind = 0;
2222 return(KEY_DOWN);
2224 case KEY_HOME :
2225 case (CTRL|'A') : /* goto beginning of line */
2226 ods.p_ind = 0;
2227 continue;
2229 case KEY_END :
2230 case (CTRL|'E') : /* goto end of line */
2231 ods.p_ind = ods.p_len;
2232 continue;
2234 case (CTRL|'D') : /* blast this char */
2235 case KEY_DEL :
2236 if(!allowedit){
2237 rdonly();
2238 continue;
2240 else if(ods.p_ind >= ucs4_strlen(strng))
2241 continue;
2243 if(headents[ods.cur_e].is_attach && intag(strng, ods.p_ind)){
2244 emlwwrite(_("Can't edit attachment number!"), NULL);
2245 continue;
2248 pputc(strng[ods.p_ind++], 0); /* drop through and rubout */
2250 case DEL : /* blast previous char */
2251 case (CTRL|'H') :
2252 if(!allowedit){
2253 rdonly();
2254 continue;
2257 if(headents[ods.cur_e].is_attach && intag(strng, ods.p_ind-1)){
2258 emlwwrite(_("Can't edit attachment number!"), NULL);
2259 continue;
2262 if(ods.p_ind > 0){ /* just shift left one char */
2263 ods.p_len--;
2264 headents[ods.cur_e].dirty = 1;
2265 if(ods.p_len == 0)
2266 headents[ods.cur_e].sticky = 0;
2267 else
2268 mark_sticky(&headents[ods.cur_e]);
2270 tbufp = &strng[--ods.p_ind];
2271 while(*tbufp++ != '\0')
2272 tbufp[-1] = *tbufp;
2273 tbufp = &strng[ods.p_ind];
2274 if(pdel()) /* physical screen delete */
2275 skipmove++; /* must'a been optimal */
2277 else{ /* may have work to do */
2278 if(ods.cur_l->prev == NULL){
2279 (*term.t_beep)(); /* no erase into next field */
2280 continue;
2283 ods.p_line--;
2284 ods.cur_l = ods.cur_l->prev;
2285 strng = ods.cur_l->text;
2286 if((i=ucs4_strlen(strng)) > 0){
2287 strng[i-1] = '\0'; /* erase the character */
2288 ods.p_ind = i-1;
2290 else{
2291 headents[ods.cur_e].sticky = 0;
2292 ods.p_ind = 0;
2295 tbufp = &strng[ods.p_ind];
2298 if((status = FormatLines(ods.cur_l, "", LINEWID(),
2299 headents[ods.cur_e].break_on_comma,0))==-1){
2300 (*term.t_beep)();
2301 continue;
2303 else{
2305 * beware, the dot may have moved...
2307 while((ods.p_len=ucs4_strlen(strng)) < ods.p_ind){
2308 ods.p_line++;
2309 ods.p_ind -= ucs4_strlen(strng);
2310 ods.cur_l = ods.cur_l->next;
2311 strng = ods.cur_l->text;
2312 ods.p_len = ucs4_strlen(strng);
2313 tbufp = &strng[ods.p_ind];
2314 status = TRUE;
2317 if(UpdateHeader(0))
2318 status = TRUE;
2320 PaintHeader(COMPOSER_TOP_LINE, FALSE);
2321 if(status == TRUE)
2322 PaintBody(1);
2325 movecursor(ods.p_line,
2326 ucs4_str_width_ptr_to_ptr(strng, &strng[ods.p_ind])+headents[ods.cur_e].prwid);
2328 if(skipmove)
2329 continue;
2331 break;
2333 default :
2334 return(ch);
2338 while ((tbufp-strng) < HLSZ && *tbufp != '\0') /* synchronizing loop */
2339 pputc(*tbufp++, 0);
2341 if(ucs4_str_width_ptr_to_ptr(&strng[0], &strng[ods.p_len]) < LINEWID())
2342 peeol();
2347 void
2348 HeaderPaintCursor(void)
2350 movecursor(ods.p_line, ucs4_str_width_ptr_to_ptr(&ods.cur_l->text[0], &ods.cur_l->text[ods.p_ind])+headents[ods.cur_e].prwid);
2356 * FormatLines - Place the given text at the front of the given line->text
2357 * making sure to properly format the line, then check
2358 * all lines below for proper format.
2360 * notes:
2361 * Not much optimization at all. Right now, it recursively
2362 * fixes all remaining lines in the entry. Some speed might
2363 * gained if this was built to iteratively scan the lines.
2365 * returns:
2366 * -1 on error
2367 * FALSE if only this line is changed
2368 * TRUE if text below the first line is changed
2371 FormatLines(struct hdr_line *h, /* where to begin formatting */
2372 char *utf8_instr, /* input string */
2373 int maxwid, /* max width of a line */
2374 int break_on_comma, /* break lines on commas */
2375 int quoted) /* this line inside quotes */
2377 int retval = FALSE;
2378 int i, l, len;
2379 char *utf8;
2380 UCS *ostr; /* pointer to output string */
2381 UCS *free_istr = NULL;
2382 UCS *istr = NULL;
2383 UCS *breakp; /* pointer to line break */
2384 UCS *bp, *tp; /* temporary pointers */
2385 UCS *buf; /* string to add later */
2386 struct hdr_line *nlp, *lp;
2388 ostr = h->text;
2389 nlp = h->next;
2390 if(utf8_instr)
2391 free_istr = istr = utf8_to_ucs4_cpystr(utf8_instr);
2393 len = ucs4_strlen(istr) + ucs4_strlen(ostr);
2394 if((buf = (UCS *) malloc((len+10) * sizeof(*buf))) == NULL){
2395 if(free_istr)
2396 fs_give((void **) &free_istr);
2398 return(-1);
2401 if(ucs4_str_width(istr) + ucs4_str_width(ostr) >= maxwid){ /* break then fixup below */
2403 if((l=ucs4_str_width(istr)) < maxwid){ /* room for more */
2405 if(break_on_comma && (bp = ucs4_strqchr(istr, ',', &quoted, -1))){
2406 bp += (bp[1] == ' ') ? 2 : 1;
2407 for(tp = bp; *tp && *tp == ' '; tp++)
2410 ucs4_strncpy(buf, tp, len+10);
2411 buf[len+10-1] = '\0';
2412 ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1);
2413 buf[len+10-1] = '\0';
2415 for(i = 0; &istr[i] < bp; i++)
2416 ostr[i] = istr[i];
2418 ostr[i] = '\0';
2419 retval = TRUE;
2421 else{
2422 breakp = break_point(ostr, maxwid-ucs4_str_width(istr),
2423 break_on_comma ? ',' : ' ',
2424 break_on_comma ? &quoted : NULL);
2426 if(breakp == ostr){ /* no good breakpoint */
2427 if(break_on_comma && *breakp == ','){
2428 breakp = ostr + 1;
2429 retval = TRUE;
2431 else if(ucs4_strchr(istr,(break_on_comma && !quoted)?',':' ')){
2432 ucs4_strncpy(buf, ostr, len+10);
2433 buf[len+10-1] = '\0';
2434 ucs4_strncpy(ostr, istr, HLSZ);
2435 ostr[HLSZ-1] = '\0';
2437 else{ /* istr's broken as we can get it */
2439 * Break at maxwid - width of istr
2441 breakp = ucs4_particular_width(ostr, maxwid - ucs4_str_width(istr)-1);
2442 retval = TRUE;
2445 else
2446 retval = TRUE;
2448 if(retval){
2449 ucs4_strncpy(buf, breakp, len+10); /* save broken line */
2450 buf[len+10-1] = '\0';
2451 if(breakp == ostr){
2452 ucs4_strncpy(ostr, istr, HLSZ); /* simple if no break */
2453 ostr[HLSZ-1] = '\0';
2455 else{
2456 *breakp = '\0'; /* more work to break it */
2457 i = ucs4_strlen(istr);
2459 * shift ostr i chars
2461 for(bp=breakp; bp >= ostr && i; bp--)
2462 *(bp+i) = *bp;
2463 for(tp=ostr, bp=istr; *bp != '\0'; tp++, bp++)
2464 *tp = *bp; /* then add istr */
2470 * Short-circuit the recursion in this case.
2471 * No time right now to figure out how to do it better.
2473 else if(l > 2*maxwid){
2474 UCS *istrp, *saveostr = NULL;
2476 retval = TRUE;
2477 if(ostr && *ostr)
2478 saveostr = ucs4_cpystr(ostr);
2480 istrp = istr;
2481 while(l > 2*maxwid){
2482 if(break_on_comma || maxwid == 1){
2483 breakp = (!(bp = ucs4_strqchr(istrp, ',', &quoted, maxwid))
2484 || ucs4_str_width_ptr_to_ptr(istrp, bp) >= maxwid || maxwid == 1)
2485 ? ucs4_particular_width(istrp, maxwid)
2486 : bp + ((bp[1] == ' ') ? 2 : 1);
2488 else{
2489 breakp = break_point(istrp, maxwid, ' ', NULL);
2491 if(breakp == istrp) /* no good break point */
2492 breakp = ucs4_particular_width(istrp, maxwid-1);
2495 for(tp=ostr,bp=istrp; bp < breakp; tp++, bp++)
2496 *tp = *bp;
2498 *tp = '\0';
2499 l -= ucs4_str_width_ptr_to_ptr(istrp, breakp);
2500 istrp = breakp;
2502 if((lp = HALLOC()) == NULL){
2503 emlwrite("Can't allocate any more lines for header!", NULL);
2504 free(buf);
2505 if(free_istr)
2506 fs_give((void **) &free_istr);
2508 return(-1);
2511 lp->next = h->next;
2512 if(h->next)
2513 h->next->prev = lp;
2515 h->next = lp;
2516 lp->prev = h;
2517 lp->text[0] = '\0';
2518 h = h->next;
2519 ostr = h->text;
2523 * Now l is still > maxwid. Do it the recursive way,
2524 * like the else clause below. Surely we could fix up the
2525 * flow control some here, but this works for now.
2528 nlp = h->next;
2529 istr = istrp;
2530 if(saveostr){
2531 ucs4_strncpy(ostr, saveostr, HLSZ);
2532 ostr[HLSZ-1] = '\0';
2533 fs_give((void **) &saveostr);
2536 if(break_on_comma || maxwid == 1){
2537 breakp = (!(bp = ucs4_strqchr(istrp, ',', &quoted, maxwid))
2538 || ucs4_str_width_ptr_to_ptr(istrp, bp) >= maxwid || maxwid == 1)
2539 ? ucs4_particular_width(istrp, maxwid)
2540 : bp + ((bp[1] == ' ') ? 2 : 1);
2542 else{
2543 breakp = break_point(istrp, maxwid, ' ', NULL);
2545 if(breakp == istrp) /* no good break point */
2546 breakp = ucs4_particular_width(istrp, maxwid-1);
2549 ucs4_strncpy(buf, breakp, len+10); /* save broken line */
2550 buf[len+10-1] = '\0';
2551 ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1); /* add line that was there */
2552 buf[len+10-1] = '\0';
2554 for(tp=ostr,bp=istr; bp < breakp; tp++, bp++)
2555 *tp = *bp;
2557 *tp = '\0';
2559 else{ /* utf8_instr > maxwid ! */
2560 if(break_on_comma || maxwid == 1){
2561 breakp = (!(bp = ucs4_strqchr(istr, ',', &quoted, maxwid))
2562 || ucs4_str_width_ptr_to_ptr(istr, bp) >= maxwid || maxwid == 1)
2563 ? ucs4_particular_width(istr, maxwid)
2564 : bp + ((bp[1] == ' ') ? 2 : 1);
2566 else{
2567 breakp = break_point(istr, maxwid, ' ', NULL);
2569 if(breakp == istr) /* no good break point */
2570 breakp = ucs4_particular_width(istr, maxwid-1);
2573 ucs4_strncpy(buf, breakp, len+10); /* save broken line */
2574 buf[len+10-1] = '\0';
2575 ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1); /* add line that was there */
2576 buf[len+10-1] = '\0';
2578 for(tp=ostr,bp=istr; bp < breakp; tp++, bp++)
2579 *tp = *bp;
2581 *tp = '\0';
2584 if(nlp == NULL){ /* no place to add below? */
2585 if((lp = HALLOC()) == NULL){
2586 emlwrite("Can't allocate any more lines for header!", NULL);
2587 free(buf);
2588 if(free_istr)
2589 fs_give((void **) &free_istr);
2591 return(-1);
2594 if(TERM_OPTIMIZE && (i = physical_line(h)) != -1)
2595 scrolldown(wheadp, i - 1, 1);
2597 h->next = lp; /* fix up links */
2598 lp->prev = h;
2599 lp->next = NULL;
2600 lp->text[0] = '\0';
2601 nlp = lp;
2602 retval = TRUE;
2605 else{ /* combined width < max */
2606 buf[0] = '\0';
2607 if(istr && *istr){
2608 ucs4_strncpy(buf, istr, len+10); /* insert istr before ostr */
2609 buf[len+10-1] = '\0';
2611 ucs4_strncat(buf, ostr, len+10-ucs4_strlen(buf)-1);
2612 buf[len+10-1] = '\0';
2614 ucs4_strncpy(ostr, buf, HLSZ); /* copy back to ostr */
2615 ostr[HLSZ-1] = '\0';
2618 *buf = '\0';
2619 breakp = NULL;
2621 if(break_on_comma && (breakp = ucs4_strqchr(ostr, ',', &quoted, -1))){
2622 breakp += (breakp[1] == ' ') ? 2 : 1;
2623 ucs4_strncpy(buf, breakp, len+10);
2624 buf[len+10-1] = '\0';
2625 *breakp = '\0';
2627 if(*buf && !nlp){
2628 if((lp = HALLOC()) == NULL){
2629 emlwrite("Can't allocate any more lines for header!",NULL);
2630 free(buf);
2631 if(free_istr)
2632 fs_give((void **) &free_istr);
2634 return(-1);
2637 if(TERM_OPTIMIZE && (i = physical_line(h)) != -1)
2638 scrolldown(wheadp, i - 1, 1);
2640 h->next = lp; /* fix up links */
2641 lp->prev = h;
2642 lp->next = NULL;
2643 lp->text[0] = '\0';
2644 nlp = lp;
2645 retval = TRUE;
2649 if(nlp){
2650 if(!*buf && !breakp){
2651 if(ucs4_str_width(ostr) + ucs4_str_width(nlp->text) >= maxwid){
2652 breakp = break_point(nlp->text, maxwid-ucs4_str_width(ostr),
2653 break_on_comma ? ',' : ' ',
2654 break_on_comma ? &quoted : NULL);
2656 if(breakp == nlp->text){ /* commas this line? */
2657 for(tp=ostr; *tp && *tp != ' '; tp++)
2660 if(!*tp){ /* no commas, get next best */
2661 breakp += maxwid - ucs4_str_width(ostr) - 1;
2662 retval = TRUE;
2664 else
2665 retval = FALSE;
2667 else
2668 retval = TRUE;
2670 if(retval){ /* only if something to do */
2671 for(tp = &ostr[ucs4_strlen(ostr)],bp=nlp->text; bp<breakp;
2672 tp++, bp++)
2673 *tp = *bp; /* add breakp to this line */
2674 *tp = '\0';
2675 for(tp=nlp->text, bp=breakp; *bp != '\0'; tp++, bp++)
2676 *tp = *bp; /* shift next line to left */
2677 *tp = '\0';
2680 else{
2681 ucs4_strncat(ostr, nlp->text, HLSZ-ucs4_strlen(ostr)-1);
2682 ostr[HLSZ-1] = '\0';
2684 if(TERM_OPTIMIZE && (i = physical_line(nlp)) != -1)
2685 scrollup(wheadp, i, 1);
2687 hldelete(nlp);
2689 if(!(nlp = h->next)){
2690 free(buf);
2691 if(free_istr)
2692 fs_give((void **) &free_istr);
2694 return(TRUE); /* can't go further */
2696 else
2697 retval = TRUE; /* more work to do? */
2701 else{
2702 free(buf);
2703 if(free_istr)
2704 fs_give((void **) &free_istr);
2706 return(FALSE);
2711 utf8 = ucs4_to_utf8_cpystr(buf);
2712 free(buf);
2713 if(free_istr)
2714 fs_give((void **) &free_istr);
2716 if(utf8){
2717 int rv;
2719 i = FormatLines(nlp, utf8, maxwid, break_on_comma, quoted);
2720 fs_give((void **) &utf8);
2721 switch(i){
2722 case -1: /* bubble up worst case */
2723 rv = -1;
2724 break;
2725 case FALSE:
2726 if(retval == FALSE){
2727 rv = FALSE;
2728 break;
2730 default:
2731 rv = TRUE;
2734 return(rv);
2736 else
2737 return(-1);
2741 * Format the lines before parsing attachments so we
2742 * don't expand a bunch of attachments that we don't
2743 * have the buffer space for.
2746 FormatSyncAttach(void)
2748 FormatLines(headents[ods.cur_e].hd_text, "",
2749 term.t_ncol - headents[ods.cur_e].prwid,
2750 headents[ods.cur_e].break_on_comma, 0);
2751 return(SyncAttach());
2756 * PaintHeader - do the work of displaying the header from the given
2757 * physical screen line the end of the header.
2759 * 17 July 91 - fixed reshow to deal with arbitrarily large headers.
2761 void
2762 PaintHeader(int line, /* physical line on screen */
2763 int clear) /* clear before painting */
2765 struct hdr_line *lp;
2766 int curline;
2767 int curindex;
2768 int curoffset;
2769 UCS buf[NLINE];
2770 UCS *bufp;
2771 int i, e, w;
2772 COLOR_PAIR *lastc = NULL;
2774 if(Pmaster && Pmaster->colors){
2775 lastc = pico_get_cur_color();
2776 pico_set_colorp(Pmaster->colors->ntcp, PSC_NONE);
2779 if(clear)
2780 pclear(COMPOSER_TOP_LINE, ComposerTopLine-1);
2782 curline = COMPOSER_TOP_LINE;
2783 curindex = curoffset = 0;
2785 for(lp = ods.top_l, e = ods.top_e; ; curline++){
2786 if((curline == line) || ((lp = next_hline(&e, lp)) == NULL))
2787 break;
2790 while(headents[e].name != NULL){ /* begin to redraw */
2791 while(lp != NULL){
2792 buf[0] = '\0';
2793 if((!lp->prev || curline == COMPOSER_TOP_LINE) && !curoffset){
2794 if(InvertPrompt(e, (e == ods.cur_e && ComposerEditing)) == -1
2795 && !is_blank(curline, 0, headents[e].prwid)){
2796 for(i = 0; i < headents[e].prwid; i++)
2797 buf[i] = ' ';
2799 buf[i] = '\0';
2802 else if(!is_blank(curline, 0, headents[e].prwid)){
2803 for(i = 0; i < headents[e].prwid; i++)
2804 buf[i] = ' ';
2806 buf[i] = '\0';
2809 if(*(bufp = buf) != '\0'){ /* need to paint? */
2810 movecursor(curline, 0); /* paint the line... */
2811 while(*bufp != '\0')
2812 pputc(*bufp++, 0);
2815 bufp = &(lp->text[curindex]); /* skip chars already there */
2816 curoffset += headents[e].prwid;
2817 curindex = index_from_col(curline, curoffset);
2818 while(pscr(curline, curindex) != NULL &&
2819 *bufp == pscr(curline, curindex)->c && *bufp != '\0'){
2820 w = wcellwidth(*bufp);
2821 curoffset += (w >= 0 ? w : 1);
2822 ++bufp;
2823 ++curindex;
2824 if(curoffset >= term.t_ncol)
2825 break;
2828 if(*bufp != '\0'){ /* need to move? */
2829 movecursor(curline, curoffset);
2830 while(*bufp != '\0'){ /* display what's not there */
2831 pputc(*bufp, 0);
2832 w = wcellwidth(*bufp);
2833 curoffset += (w >= 0 ? w : 1);
2834 ++bufp;
2835 ++curindex;
2839 if(curoffset < term.t_ncol){
2840 movecursor(curline, curoffset);
2841 peeol();
2843 curline++;
2844 curindex = curoffset = 0;
2845 if(curline >= BOTTOM())
2846 break;
2848 lp = lp->next;
2851 if(curline >= BOTTOM())
2852 return; /* don't paint delimiter */
2854 while(headents[++e].name != NULL)
2855 if(headents[e].display_it){
2856 lp = headents[e].hd_text;
2857 break;
2861 display_delimiter(ComposerEditing ? 0 : 1);
2863 if(lastc){
2864 pico_set_colorp(lastc, PSC_NONE);
2865 free_color_pair(&lastc);
2872 * PaintBody() - generic call to handle repainting everything BUT the
2873 * header
2875 * notes:
2876 * The header redrawing in a level 0 body paint gets done
2877 * in update()
2879 void
2880 PaintBody(int level)
2882 curwp->w_flag |= WFHARD; /* make sure framing's right */
2883 if(level == 0) /* specify what to update */
2884 sgarbf = TRUE;
2886 update(); /* display message body */
2888 if(level == 0 && ComposerEditing){
2889 mlerase(); /* clear the error line */
2890 ShowPrompt();
2896 * display_for_send - paint the composer from the top line and return.
2898 void
2899 display_for_send(void)
2901 int i = 0;
2902 struct hdr_line *l;
2904 /* if first header line isn't displayed, there's work to do */
2905 if(headents && ((l = first_hline(&i)) != ods.top_l
2906 || ComposerTopLine == COMPOSER_TOP_LINE
2907 || !ods.p_line)){
2908 struct on_display orig_ods;
2909 int orig_edit = ComposerEditing,
2910 orig_ct_line = ComposerTopLine;
2913 * fake that the cursor's in the first header line
2914 * and force repaint...
2916 orig_ods = ods;
2917 ods.cur_e = i;
2918 ods.top_l = ods.cur_l = l;
2919 ods.top_e = ods.cur_e;
2920 ods.p_line = COMPOSER_TOP_LINE;
2921 ComposerEditing = TRUE; /* to fool update() */
2922 setimark(FALSE, 1); /* remember where we were */
2923 gotobob(FALSE, 1);
2925 UpdateHeader(0); /* redraw whole enchilada */
2926 PaintHeader(COMPOSER_TOP_LINE, TRUE);
2927 PaintBody(0);
2929 ods = orig_ods; /* restore original state */
2930 ComposerEditing = orig_edit;
2931 ComposerTopLine = curwp->w_toprow = orig_ct_line;
2932 curwp->w_ntrows = BOTTOM() - ComposerTopLine;
2933 swapimark(FALSE, 1);
2935 /* in case we don't exit, set up restoring the screen */
2936 sgarbf = TRUE; /* force redraw */
2942 * ArrangeHeader - set up display parm such that header is reasonably
2943 * displayed
2945 void
2946 ArrangeHeader(void)
2948 int e;
2949 register struct hdr_line *l;
2951 ods.p_line = ods.p_ind = 0;
2952 e = ods.top_e = 0;
2953 l = ods.top_l = headents[e].hd_text;
2954 while(headents[e+1].name || (l && l->next))
2955 if((l = next_sel_hline(&e, l)) != NULL){
2956 ods.cur_l = l;
2957 ods.cur_e = e;
2960 UpdateHeader(1);
2965 * ComposerHelp() - display mail help in a context sensitive way
2966 * based on the level passed ...
2969 ComposerHelp(int level)
2971 char buf[80];
2972 VARS_TO_SAVE *saved_state;
2974 curwp->w_flag |= WFMODE;
2975 sgarbf = TRUE;
2977 if(level < 0 || !headents[level].name){
2978 (*term.t_beep)();
2979 emlwrite("Sorry, I can't help you with that.", NULL);
2980 sleep(2);
2981 return(FALSE);
2984 snprintf(buf, sizeof(buf), "Help for %s %.40s Field",
2985 (Pmaster->pine_flags & MDHDRONLY) ? "Address Book"
2986 : "Composer",
2987 headents[level].name);
2988 saved_state = save_pico_state();
2989 (*Pmaster->helper)(headents[level].help, buf, 1);
2990 if(saved_state){
2991 restore_pico_state(saved_state);
2992 free_pico_state(saved_state);
2995 ttresize();
2996 picosigs(); /* restore altered handlers */
2997 return(TRUE);
3003 * ToggleHeader() - set or unset pico values to the full screen size
3004 * painting header if need be.
3007 ToggleHeader(int show)
3010 * check to see if we need to display the header...
3012 if(show){
3013 UpdateHeader(0); /* figure bounds */
3014 PaintHeader(COMPOSER_TOP_LINE, FALSE); /* draw it */
3016 else{
3018 * set bounds for no header display
3020 curwp->w_toprow = ComposerTopLine = COMPOSER_TOP_LINE;
3021 curwp->w_ntrows = BOTTOM() - ComposerTopLine;
3023 return(TRUE);
3029 * HeaderLen() - return the length in lines of the exposed portion of the
3030 * header
3033 HeaderLen(void)
3035 register struct hdr_line *lp;
3036 int e;
3037 int i;
3039 i = 1;
3040 lp = ods.top_l;
3041 e = ods.top_e;
3042 while(lp != NULL){
3043 lp = next_hline(&e, lp);
3044 i++;
3046 return(i);
3052 * first_hline() - return a pointer to the first displayable header line
3054 * returns:
3055 * 1) pointer to first displayable line in header and header
3056 * entry, via side effect, that the first line is a part of
3057 * 2) NULL if no next line, leaving entry at LASTHDR
3059 struct hdr_line *
3060 first_hline(int *entry)
3062 /* init *entry so we're sure to start from the top */
3063 for(*entry = 0; headents[*entry].name; (*entry)++)
3064 if(headents[*entry].display_it)
3065 return(headents[*entry].hd_text);
3067 *entry = 0;
3068 return(NULL); /* this shouldn't happen */
3073 * first_sel_hline() - return a pointer to the first selectable header line
3075 * returns:
3076 * 1) pointer to first selectable line in header and header
3077 * entry, via side effect, that the first line is a part of
3078 * 2) NULL if no next line, leaving entry at LASTHDR
3080 struct hdr_line *
3081 first_sel_hline(int *entry)
3083 /* init *entry so we're sure to start from the top */
3084 for(*entry = 0; headents[*entry].name; (*entry)++)
3085 if(headents[*entry].display_it && !headents[*entry].blank)
3086 return(headents[*entry].hd_text);
3088 *entry = 0;
3089 return(NULL); /* this shouldn't happen */
3095 * next_hline() - return a pointer to the next line structure
3097 * returns:
3098 * 1) pointer to next displayable line in header and header
3099 * entry, via side effect, that the next line is a part of
3100 * 2) NULL if no next line, leaving entry at LASTHDR
3102 struct hdr_line *
3103 next_hline(int *entry, struct hdr_line *line)
3105 if(line == NULL)
3106 return(NULL);
3108 if(line->next == NULL){
3109 while(headents[++(*entry)].name != NULL){
3110 if(headents[*entry].display_it)
3111 return(headents[*entry].hd_text);
3113 --(*entry);
3114 return(NULL);
3116 else
3117 return(line->next);
3122 * next_sel_hline() - return a pointer to the next selectable line structure
3124 * returns:
3125 * 1) pointer to next selectable line in header and header
3126 * entry, via side effect, that the next line is a part of
3127 * 2) NULL if no next line, leaving entry at LASTHDR
3129 struct hdr_line *
3130 next_sel_hline(int *entry, struct hdr_line *line)
3132 if(line == NULL)
3133 return(NULL);
3135 if(line->next == NULL){
3136 while(headents[++(*entry)].name != NULL){
3137 if(headents[*entry].display_it && !headents[*entry].blank)
3138 return(headents[*entry].hd_text);
3140 --(*entry);
3141 return(NULL);
3143 else
3144 return(line->next);
3150 * prev_hline() - return a pointer to the next line structure back
3152 * returns:
3153 * 1) pointer to previous displayable line in header and
3154 * the header entry that the next line is a part of
3155 * via side effect
3156 * 2) NULL if no next line, leaving entry unchanged from
3157 * the value it had on entry.
3159 struct hdr_line *
3160 prev_hline(int *entry, struct hdr_line *line)
3162 if(line == NULL)
3163 return(NULL);
3165 if(line->prev == NULL){
3166 int orig_entry;
3168 orig_entry = *entry;
3169 while(--(*entry) >= 0){
3170 if(headents[*entry].display_it){
3171 line = headents[*entry].hd_text;
3172 while(line->next != NULL)
3173 line = line->next;
3174 return(line);
3178 *entry = orig_entry;
3179 return(NULL);
3181 else
3182 return(line->prev);
3187 * prev_sel_hline() - return a pointer to the previous selectable line
3189 * returns:
3190 * 1) pointer to previous selectable line in header and
3191 * the header entry that the next line is a part of
3192 * via side effect
3193 * 2) NULL if no next line, leaving entry unchanged from
3194 * the value it had on entry.
3196 struct hdr_line *
3197 prev_sel_hline(int *entry, struct hdr_line *line)
3199 if(line == NULL)
3200 return(NULL);
3202 if(line->prev == NULL){
3203 int orig_entry;
3205 orig_entry = *entry;
3206 while(--(*entry) >= 0){
3207 if(headents[*entry].display_it && !headents[*entry].blank){
3208 line = headents[*entry].hd_text;
3209 while(line->next != NULL)
3210 line = line->next;
3211 return(line);
3215 *entry = orig_entry;
3216 return(NULL);
3218 else
3219 return(line->prev);
3225 * first_requested_hline() - return pointer to first line that pico's caller
3226 * asked that we start on.
3228 struct hdr_line *
3229 first_requested_hline(int *ent)
3231 int i, reqfield;
3232 struct hdr_line *rv = NULL;
3234 for(reqfield = -1, i = 0; headents[i].name; i++)
3235 if(headents[i].start_here){
3236 headents[i].start_here = 0; /* clear old setting */
3237 if(reqfield < 0){ /* if not already, set up */
3238 headents[i].display_it = 1; /* make sure it's shown */
3239 *ent = reqfield = i;
3240 rv = headents[i].hd_text;
3244 return(rv);
3250 * UpdateHeader() - determines the best range of lines to be displayed
3251 * using the global ods value for the current line and the
3252 * top line, also sets ComposerTopLine and pico limits
3254 * showtop -- Attempt to show all header lines if they'll fit.
3256 * notes:
3257 * This is pretty ugly because it has to keep the current line
3258 * on the screen in a reasonable location no matter what.
3259 * There are also a couple of rules to follow:
3260 * 1) follow paging conventions of pico (ie, half page
3261 * scroll)
3262 * 2) if more than one page, always display last half when
3263 * pline is toward the end of the header
3265 * returns:
3266 * TRUE if anything changed (side effects: new p_line, top_l
3267 * top_e, and pico parms)
3268 * FALSE if nothing changed
3272 UpdateHeader(int showtop)
3274 register struct hdr_line *lp;
3275 int i, le;
3276 int ret = FALSE;
3277 int old_top = ComposerTopLine;
3278 int old_p = ods.p_line;
3280 if(ods.p_line < COMPOSER_TOP_LINE ||
3281 ((ods.p_line == ComposerTopLine-2) ? 2: 0) + ods.p_line >= BOTTOM()){
3282 /* NewTop if cur header line is at bottom of screen or two from */
3283 /* the bottom of the screen if cur line is bottom header line */
3284 NewTop(showtop); /* get new top_l */
3285 ret = TRUE;
3287 else{ /* make sure p_line's OK */
3288 i = COMPOSER_TOP_LINE;
3289 lp = ods.top_l;
3290 le = ods.top_e;
3291 while(lp != ods.cur_l){
3293 * this checks to make sure cur_l is below top_l and that
3294 * cur_l is on the screen...
3296 if((lp = next_hline(&le, lp)) == NULL || ++i >= BOTTOM()){
3297 NewTop(0);
3298 ret = TRUE;
3299 break;
3304 ods.p_line = COMPOSER_TOP_LINE; /* find p_line... */
3305 lp = ods.top_l;
3306 le = ods.top_e;
3307 while(lp && lp != ods.cur_l){
3308 lp = next_hline(&le, lp);
3309 ods.p_line++;
3312 if(!ret)
3313 ret = !(ods.p_line == old_p);
3315 ComposerTopLine = ods.p_line; /* figure top composer line */
3316 while(lp && ComposerTopLine <= BOTTOM()){
3317 lp = next_hline(&le, lp);
3318 ComposerTopLine += (lp) ? 1 : 2; /* allow for delim at end */
3321 if(!ret)
3322 ret = !(ComposerTopLine == old_top);
3324 if(wheadp->w_toprow != ComposerTopLine){ /* update pico params... */
3325 wheadp->w_toprow = ComposerTopLine;
3326 wheadp->w_ntrows = ((i = BOTTOM() - ComposerTopLine) > 0) ? i : 0;
3327 ret = TRUE;
3329 return(ret);
3335 * NewTop() - calculate a new top_l based on the cur_l
3337 * showtop -- Attempt to show all the header lines if they'll fit
3339 * returns:
3340 * with ods.top_l and top_e pointing at a reasonable line
3341 * entry
3343 void
3344 NewTop(int showtop)
3346 register struct hdr_line *lp;
3347 register int i;
3348 int e;
3350 lp = ods.cur_l;
3351 e = ods.cur_e;
3352 i = showtop ? FULL_SCR() : HALF_SCR();
3354 while(lp != NULL && (--i > 0)){
3355 ods.top_l = lp;
3356 ods.top_e = e;
3357 lp = prev_hline(&e, lp);
3364 * display_delimiter() - just paint the header/message body delimiter with
3365 * inverse value specified by state.
3367 void
3368 display_delimiter(int state)
3370 UCS *bufp, *buf;
3371 COLOR_PAIR *lastc = NULL;
3373 if(ComposerTopLine - 1 >= BOTTOM()) /* silently forget it */
3374 return;
3376 if(Pmaster && Pmaster->colors){
3377 lastc = pico_get_cur_color();
3378 pico_set_colorp(Pmaster->colors->ntcp, PSC_NONE);
3381 buf = utf8_to_ucs4_cpystr((gmode & MDHDRONLY) ? "" : HDR_DELIM);
3382 if(!buf)
3383 return;
3385 bufp = buf;
3387 if(state == delim_ps){ /* optimize ? */
3388 for(delim_ps = 0; bufp[delim_ps] && pscr(ComposerTopLine-1,delim_ps) != NULL && pscr(ComposerTopLine-1,delim_ps)->c == bufp[delim_ps];delim_ps++)
3391 if(bufp[delim_ps] == '\0' && !(gmode & MDHDRONLY)){
3392 delim_ps = state;
3393 fs_give((void **) &buf);
3394 if(lastc) free_color_pair(&lastc);
3395 return; /* already displayed! */
3399 delim_ps = state;
3401 movecursor(ComposerTopLine - 1, 0);
3402 if(state)
3403 (*term.t_rev)(1);
3404 else if (*term.t_eri)
3405 (*term.t_eri)();
3407 while(*bufp != '\0')
3408 pputc(*bufp++, state ? 1 : 0);
3410 if(state)
3411 (*term.t_rev)(0);
3413 peeol();
3414 fs_give((void **) &buf);
3416 if(lastc){
3417 pico_set_colorp(lastc, PSC_NONE);
3418 free_color_pair(&lastc);
3425 * InvertPrompt() - invert the prompt associated with header entry to state
3426 * state (true if invert, false otherwise).
3427 * returns:
3428 * non-zero if nothing done
3429 * 0 if prompt inverted successfully
3431 * notes:
3432 * come to think of it, this func and the one above could
3433 * easily be combined
3436 InvertPrompt(int entry, int state)
3438 UCS *buf, *bufp;
3439 UCS *end;
3440 int i;
3442 buf = utf8_to_ucs4_cpystr(headents[entry].prompt); /* fresh prompt paint */
3443 if(!buf)
3444 return(-1);
3446 bufp = buf;
3447 if((i = entry_line(entry, FALSE)) == -1){
3448 fs_give((void **) &buf);
3449 return(-1); /* silently forget it */
3452 end = buf + ucs4_strlen(buf);
3455 * Makes sure that the prompt doesn't take up more than prwid of screen space.
3456 * The caller should do that, too, in order to make it look right so
3457 * this should most likely be a no-op
3459 if(ucs4_str_width_ptr_to_ptr(buf, end) > headents[entry].prwid){
3460 end = ucs4_particular_width(buf, headents[entry].prwid);
3461 *end = '\0';
3464 if(entry < 16 && (invert_ps&(1<<entry))
3465 == (state ? 1<<entry : 0)){ /* optimize ? */
3466 int j;
3468 for(j = 0; bufp[j] && pscr(i, j)->c == bufp[j]; j++)
3471 if(bufp[j] == '\0'){
3472 if(state)
3473 invert_ps |= 1<<entry;
3474 else
3475 invert_ps &= ~(1<<entry);
3477 fs_give((void **) &buf);
3478 return(0); /* already displayed! */
3482 if(entry < 16){ /* if > 16, cannot be stored in invert_ps */
3483 if(state)
3484 invert_ps |= 1<<entry;
3485 else
3486 invert_ps &= ~(1<<entry);
3489 movecursor(i, 0);
3490 if(state)
3491 (*term.t_rev)(1);
3492 else if (*term.t_eri)
3493 (*term.t_eri)();
3495 while(*bufp && *(bufp + 1))
3496 pputc(*bufp++, 1); /* putc up to last char */
3498 if(state)
3499 (*term.t_rev)(0);
3501 pputc(*bufp, 0); /* last char not inverted */
3503 fs_give((void **) &buf);
3505 return(TRUE);
3511 * partial_entries() - toggle display of the bcc and fcc fields.
3513 * returns:
3514 * TRUE if there are partial entries on the display
3515 * FALSE otherwise.
3518 partial_entries(void)
3520 register struct headerentry *h;
3521 int is_on;
3523 /*---- find out status of first rich header ---*/
3524 for(h = headents; !h->rich_header && h->name != NULL; h++)
3527 is_on = h->display_it;
3528 for(h = headents; h->name != NULL; h++)
3529 if(h->rich_header)
3530 h->display_it = ! is_on;
3532 return(is_on);
3538 * entry_line() - return the physical line on the screen associated
3539 * with the given header entry field. Note: the field
3540 * may span lines, so if the last char is set, return
3541 * the appropriate value.
3543 * returns:
3544 * 1) physical line number of entry
3545 * 2) -1 if entry currently not on display
3548 entry_line(int entry, int lastchar)
3550 register int p_line = COMPOSER_TOP_LINE;
3551 int i;
3552 register struct hdr_line *line;
3554 for(line = ods.top_l, i = ods.top_e;
3555 headents && headents[i].name && i <= entry;
3556 p_line++){
3557 if(p_line >= BOTTOM())
3558 break;
3559 if(i == entry){
3560 if(lastchar){
3561 if(line->next == NULL)
3562 return(p_line);
3564 else if(line->prev == NULL)
3565 return(p_line);
3566 else
3567 return(-1);
3569 line = next_hline(&i, line);
3571 return(-1);
3577 * physical_line() - return the physical line on the screen associated
3578 * with the given header line pointer.
3580 * returns:
3581 * 1) physical line number of entry
3582 * 2) -1 if entry currently not on display
3585 physical_line(struct hdr_line *l)
3587 register int p_line = COMPOSER_TOP_LINE;
3588 register struct hdr_line *lp;
3589 int i;
3591 for(lp=ods.top_l, i=ods.top_e; headents[i].name && lp != NULL; p_line++){
3592 if(p_line >= BOTTOM())
3593 break;
3595 if(lp == l)
3596 return(p_line);
3598 lp = next_hline(&i, lp);
3600 return(-1);
3606 * call_builder() - resolve any nicknames in the address book associated
3607 * with the given entry...
3609 * NOTES:
3611 * BEWARE: this function can cause cur_l and top_l to get lost so BE
3612 * CAREFUL before and after you call this function!!!
3614 * There could to be something here to resolve cur_l and top_l
3615 * reasonably into the new linked list for this entry.
3617 * The reason this would mostly work without it is resolve_niks gets
3618 * called for the most part in between fields. Since we're moving
3619 * to the beginning or end (i.e. the next/prev pointer in the old
3620 * freed cur_l is NULL) of the next entry, we get a new cur_l
3621 * pointing at a good line. Then since top_l is based on cur_l in
3622 * NewTop() we have pretty much lucked out.
3624 * Where we could get burned is in a canceled exit (ctrl|x). Here
3625 * nicknames get resolved into addresses, which invalidates cur_l
3626 * and top_l. Since we don't actually leave, we could begin editing
3627 * again with bad pointers. This would usually results in a nice
3628 * core dump.
3630 * NOTE: The mangled argument is a little strange. It's used on both
3631 * input and output. On input, if it is not set, then that tells the
3632 * builder not to do anything that might take a long time, like a
3633 * white pages lookup. On return, it tells the caller that the screen
3634 * and signals may have been mangled so signals should be reset, window
3635 * resized, and screen redrawn.
3637 * RETURNS:
3638 * > 0 if any names where resolved, otherwise
3639 * 0 if not, or
3640 * < 0 on error
3641 * -1: move to next line
3642 * -2: don't move off this line
3645 call_builder(struct headerentry *entry, int *mangled, char **err)
3647 register int retval = 0;
3648 register int i;
3649 register struct hdr_line *line;
3650 int quoted = 0;
3651 int sbuflen;
3652 char *sbuf;
3653 char *s = NULL;
3654 char *tmp;
3655 struct headerentry *e;
3656 BUILDER_ARG *nextarg, *arg = NULL, *headarg = NULL;
3657 VARS_TO_SAVE *saved_state;
3659 if(!entry->builder)
3660 return(0);
3662 line = entry->hd_text;
3663 sbuflen = 0;
3664 while(line != NULL){
3665 sbuflen += (6*term.t_ncol);
3666 line = line->next;
3668 sbuflen++;
3669 if((sbuf=(char *)malloc(sbuflen * sizeof(*sbuf))) == NULL){
3670 emlwrite("Can't malloc space to expand address", NULL);
3671 return(-1);
3674 *sbuf = '\0';
3677 * cat the whole entry into one string...
3679 line = entry->hd_text;
3680 while(line != NULL){
3681 i = ucs4_strlen(line->text);
3684 * To keep pine address builder happy, addresses should be separated
3685 * by ", ". Add this space if needed, otherwise...
3686 * (This is some ancient requirement that is no longer needed.)
3688 * If this line is NOT a continuation of the previous line, add
3689 * white space for pine's address builder if its not already there...
3690 * (This is some ancient requirement that is no longer needed.)
3692 * Also if it's not a continuation (i.e., there's already and addr on
3693 * the line), and there's another line below, treat the new line as
3694 * an implied comma.
3695 * (This should only be done for address-type lines, not for regular
3696 * text lines like subjects. Key off of the break_on_comma bit which
3697 * should only be set on those that won't mind a comma being added.)
3699 if(entry->break_on_comma){
3700 UCS *space, commaspace[3];
3702 commaspace[0] = ',';
3703 commaspace[1] = ' ';
3704 commaspace[2] = '\0';
3705 space = commaspace+1;
3707 if(i && line->text[i-1] == ','){
3708 ucs4_strncat(line->text, space, HLSZ-i-1); /* help address builder */
3709 line->text[HLSZ-1] = '\0';
3711 else if(line->next != NULL && !strend(line->text, ',')){
3712 if(ucs4_strqchr(line->text, ',', &quoted, -1)){
3713 ucs4_strncat(line->text, commaspace, HLSZ-i-1); /* implied comma */
3714 line->text[HLSZ-1] = '\0';
3717 else if(line->prev != NULL && line->next != NULL){
3718 if(ucs4_strchr(line->prev->text, ' ') != NULL
3719 && line->text[i-1] != ' '){
3720 ucs4_strncat(line->text, space, HLSZ-i-1);
3721 line->text[HLSZ-1] = '\0';
3726 tmp = ucs4_to_utf8_cpystr(line->text);
3727 if(tmp){
3728 strncat(sbuf, tmp, sbuflen-strlen(sbuf)-1);
3729 sbuf[sbuflen-1] = '\0';
3730 fs_give((void **) &tmp);
3733 line = line->next;
3736 if(entry->affected_entry){
3737 /* check if any non-sticky affected entries */
3738 for(e = entry->affected_entry; e; e = e->next_affected)
3739 if(!e->sticky)
3740 break;
3742 /* there is at least one non-sticky so make a list to pass */
3743 if(e){
3744 for(e = entry->affected_entry; e; e = e->next_affected){
3745 if(!arg){
3746 headarg = arg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG));
3747 if(!arg){
3748 emlwrite("Can't malloc space for fcc", NULL);
3749 return(-1);
3751 else{
3752 arg->next = NULL;
3753 arg->tptr = NULL;
3754 arg->aff = &(e->bldr_private);
3755 arg->me = &(entry->bldr_private);
3758 else{
3759 nextarg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG));
3760 if(!nextarg){
3761 emlwrite("Can't malloc space for fcc", NULL);
3762 return(-1);
3764 else{
3765 nextarg->next = NULL;
3766 nextarg->tptr = NULL;
3767 nextarg->aff = &(e->bldr_private);
3768 nextarg->me = &(entry->bldr_private);
3769 arg->next = nextarg;
3770 arg = arg->next;
3774 if(!e->sticky){
3775 line = e->hd_text;
3776 arg->tptr = ucs4_to_utf8_cpystr(line->text);
3783 * Even if there are no affected entries, we still need the arg
3784 * to pass the "me" pointer.
3786 if(!headarg){
3787 headarg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG));
3788 if(!headarg){
3789 emlwrite("Can't malloc space", NULL);
3790 return(-1);
3792 else{
3793 headarg->next = NULL;
3794 headarg->tptr = NULL;
3795 headarg->aff = NULL;
3796 headarg->me = &(entry->bldr_private);
3801 * The builder may make a new call back to pico() so we save and
3802 * restore the pico state.
3804 saved_state = save_pico_state();
3805 retval = (*entry->builder)(sbuf, &s, err, headarg, mangled);
3806 if(saved_state){
3807 restore_pico_state(saved_state);
3808 free_pico_state(saved_state);
3811 if(mangled && *mangled & BUILDER_MESSAGE_DISPLAYED){
3812 *mangled &= ~ BUILDER_MESSAGE_DISPLAYED;
3813 if(mpresf == FALSE)
3814 mpresf = TRUE;
3817 if(mangled && *mangled & BUILDER_FOOTER_MANGLED){
3818 *mangled &= ~ BUILDER_FOOTER_MANGLED;
3819 sgarbk = TRUE;
3820 pclear(term.t_nrow-1, term.t_nrow);
3823 if(retval >= 0){
3824 if(strcmp(sbuf, s)){
3825 line = entry->hd_text;
3826 InitEntryText(s, entry); /* arrange new one */
3827 zotentry(line); /* blast old list o'entries */
3828 entry->dirty = 1; /* mark it dirty */
3829 retval = 1;
3832 for(e = entry->affected_entry, arg = headarg;
3834 e = e->next_affected, arg = arg ? arg->next : NULL){
3835 if(!e->sticky){
3836 line = e->hd_text;
3837 tmp = ucs4_to_utf8_cpystr(line->text);
3838 if(strcmp(tmp, arg ? arg->tptr : "")){ /* it changed */
3839 /* make sure they see it if changed */
3840 e->display_it = 1;
3841 InitEntryText(arg ? arg->tptr : "", e);
3842 if(line == ods.top_l)
3843 ods.top_l = e->hd_text;
3845 zotentry(line); /* blast old list o'entries */
3846 e->dirty = 1; /* mark it dirty */
3847 retval = 1;
3850 if(tmp)
3851 fs_give((void **) &tmp);
3856 if(s)
3857 free(s);
3859 for(arg = headarg; arg; arg = nextarg){
3860 /* Don't free xtra or me, they just point to headerentry data */
3861 nextarg = arg->next;
3862 if(arg->tptr)
3863 free(arg->tptr);
3865 free(arg);
3868 free(sbuf);
3869 return(retval);
3873 void
3874 call_expander(void)
3876 char **s = NULL;
3877 VARS_TO_SAVE *saved_state;
3878 int expret;
3880 if(!Pmaster->expander)
3881 return;
3884 * Since expander may make a call back to pico() we need to
3885 * save and restore pico state.
3887 if((saved_state = save_pico_state()) != NULL){
3889 expret = (*Pmaster->expander)(headents, &s);
3891 restore_pico_state(saved_state);
3892 free_pico_state(saved_state);
3893 ttresize();
3894 picosigs();
3896 if(expret > 0 && s){
3897 char *tbuf, *p;
3898 int i, biggest = 100;
3899 struct headerentry *e;
3902 * Use tbuf to cat together multiple line entries before comparing.
3904 tbuf = (char *)malloc((biggest+1) * sizeof(*tbuf));
3905 for(e = headents, i=0; e->name != NULL; e++,i++){
3906 int sz = 0;
3907 struct hdr_line *line;
3909 while(e->name && e->blank)
3910 e++;
3912 if(e->name == NULL)
3913 continue;
3915 for(line = e->hd_text; line != NULL; line = line->next){
3916 p = ucs4_to_utf8_cpystr(line->text);
3917 if(p){
3918 sz += strlen(p);
3919 fs_give((void **) &p);
3923 if(sz > biggest){
3924 biggest = sz;
3925 free(tbuf);
3926 tbuf = (char *)malloc((biggest+1) * sizeof(*tbuf));
3929 tbuf[0] = '\0';
3930 for(line = e->hd_text; line != NULL; line = line->next){
3931 p = ucs4_to_utf8_cpystr(line->text);
3932 if(p){
3933 strncat(tbuf, p, biggest+1-strlen(tbuf)-1);
3934 tbuf[biggest] = '\0';
3935 fs_give((void **) &p);
3939 if(strcmp(tbuf, s[i])){ /* it changed */
3940 struct hdr_line *zline;
3942 line = zline = e->hd_text;
3943 InitEntryText(s[i], e);
3946 * If any of the lines for this entry are current or
3947 * top, fix that.
3949 for(; line != NULL; line = line->next){
3950 if(line == ods.top_l)
3951 ods.top_l = e->hd_text;
3953 if(line == ods.cur_l)
3954 ods.cur_l = e->hd_text;
3957 zotentry(zline); /* blast old list o'entries */
3961 free(tbuf);
3964 if(s){
3965 char **p;
3967 for(p = s; *p; p++)
3968 free(*p);
3970 free(s);
3974 return;
3979 * strend - neglecting white space, returns TRUE if c is at the
3980 * end of the given line. otherwise FALSE.
3983 strend(UCS *s, UCS ch)
3985 UCS *b;
3987 if(s == NULL || *s == '\0')
3988 return(FALSE);
3990 for(b = &s[ucs4_strlen(s)] - 1; *b && ucs4_isspace(*b); b--){
3991 if(b == s)
3992 return(FALSE);
3995 return(*b == ch);
4000 * ucs4_strqchr - returns pointer to first non-quote-enclosed occurance of ch in
4001 * the given string. otherwise NULL.
4002 * s -- the string
4003 * ch -- the character we're looking for
4004 * q -- q tells us if we start out inside quotes on entry and is set
4005 * correctly on exit.
4006 * m -- max characters we'll check for ch (set to -1 for no max)
4008 UCS *
4009 ucs4_strqchr(UCS *s, UCS ch, int *q, int m)
4011 int quoted = (q) ? *q : 0;
4013 for(; s && *s && m != 0; s++, m--){
4014 if(*s == '"'){
4015 quoted = !quoted;
4016 if(q)
4017 *q = quoted;
4020 if(!quoted && *s == ch)
4021 return(s);
4024 return(NULL);
4029 * KillHeaderLine() - kill a line in the header
4031 * notes:
4032 * This is pretty simple. Just using the emacs kill buffer
4033 * and its accompanying functions to cut the text from lines.
4035 * returns:
4036 * TRUE if hldelete worked
4037 * FALSE otherwise
4040 KillHeaderLine(struct hdr_line *l, int append)
4042 UCS *c;
4043 int i = ods.p_ind;
4044 int nl = TRUE;
4046 if(!append)
4047 kdelete();
4049 c = l->text;
4050 if (gmode & MDDTKILL){
4051 if (c[i] == '\0') /* don't insert a new line after this line*/
4052 nl = FALSE;
4053 /*put to be deleted part into kill buffer */
4054 for (i=ods.p_ind; c[i] != '\0'; i++)
4055 kinsert(c[i]);
4056 }else{
4057 while(*c != '\0') /* splat out the line */
4058 kinsert(*c++);
4061 if (nl)
4062 kinsert('\n'); /* helpful to yank in body */
4064 #ifdef _WINDOWS
4065 mswin_killbuftoclip (kremove);
4066 #endif
4068 if (gmode & MDDTKILL){
4069 if (l->text[0]=='\0'){
4071 if(l->next && l->prev)
4072 ods.cur_l = next_hline(&ods.cur_e, l);
4073 else if(l->prev)
4074 ods.cur_l = prev_hline(&ods.cur_e, l);
4076 if(l == ods.top_l)
4077 ods.top_l = ods.cur_l;
4079 return(hldelete(l));
4081 else {
4082 l->text[ods.p_ind]='\0'; /* delete part of the line from the cursor */
4083 return(TRUE);
4085 }else{
4086 if(l->next && l->prev)
4087 ods.cur_l = next_hline(&ods.cur_e, l);
4088 else if(l->prev)
4089 ods.cur_l = prev_hline(&ods.cur_e, l);
4091 if(l == ods.top_l)
4092 ods.top_l = ods.cur_l;
4094 return(hldelete(l)); /* blast it */
4101 * SaveHeaderLines() - insert the saved lines in the list before the
4102 * current line in the header
4104 * notes:
4105 * Once again, just using emacs' kill buffer and its
4106 * functions.
4108 * returns:
4109 * TRUE if something good happened
4110 * FALSE otherwise
4113 SaveHeaderLines(void)
4115 UCS *buf; /* malloc'd copy of buffer */
4116 UCS *bp; /* pointer to above buffer */
4117 register unsigned i; /* index */
4118 UCS *work_buf, *work_buf_begin;
4119 char empty[1];
4120 int len, buf_len, work_buf_len, tentative_p_ind = 0;
4121 struct hdr_line *travel, *tentative_cur_l = NULL;
4123 if(ksize()){
4124 if((bp = buf = (UCS *) malloc((ksize()+5) * sizeof(*buf))) == NULL){
4125 emlwrite("Can't malloc space for saved text", NULL);
4126 return(FALSE);
4129 else
4130 return(FALSE);
4132 for(i=0; i < ksize(); i++)
4133 if(kremove(i) != '\n') /* filter out newlines */
4134 *bp++ = (UCS) kremove(i);
4136 *bp = '\0';
4138 while(--bp >= buf) /* kill trailing white space */
4139 if(*bp != ' '){
4140 if(ods.cur_l->text[0] != '\0'){
4141 if(*bp == '>'){ /* inserting an address */
4142 *++bp = ','; /* so add separator */
4143 *++bp = '\0';
4146 else{ /* nothing in field yet */
4147 if(*bp == ','){ /* so blast any extra */
4148 *bp = '\0'; /* separators */
4151 break;
4154 /* insert new text at the dot position */
4155 buf_len = ucs4_strlen(buf);
4156 tentative_p_ind = ods.p_ind + buf_len;
4157 work_buf_len = ucs4_strlen(ods.cur_l->text) + buf_len;
4158 work_buf = (UCS *) malloc((work_buf_len + 1) * sizeof(UCS));
4159 if (work_buf == NULL) {
4160 emlwrite("Can't malloc space for saved text", NULL);
4161 return(FALSE);
4164 work_buf[0] = '\0';
4165 work_buf_begin = work_buf;
4166 i = MIN(ods.p_ind, work_buf_len);
4167 ucs4_strncpy(work_buf, ods.cur_l->text, i);
4168 work_buf[i] = '\0';
4169 ucs4_strncat(work_buf, buf, work_buf_len+1-ucs4_strlen(work_buf)-1);
4170 work_buf[work_buf_len] = '\0';
4171 ucs4_strncat(work_buf, &ods.cur_l->text[ods.p_ind], work_buf_len+1-ucs4_strlen(work_buf)-1);
4172 work_buf[work_buf_len] = '\0';
4173 empty[0]='\0';
4174 ods.p_ind = 0;
4176 i = TRUE;
4178 /* insert text in HLSZ character chunks */
4179 while(work_buf_len + ods.p_ind > HLSZ) {
4180 ucs4_strncpy(&ods.cur_l->text[ods.p_ind], work_buf, HLSZ-ods.p_ind);
4181 work_buf += (HLSZ - ods.p_ind);
4182 work_buf_len -= (HLSZ - ods.p_ind);
4184 if(FormatLines(ods.cur_l, empty, LINEWID(),
4185 headents[ods.cur_e].break_on_comma, 0) == -1) {
4186 i = FALSE;
4187 break;
4188 } else {
4189 i = TRUE;
4190 len = 0;
4191 travel = ods.cur_l;
4192 while (len < HLSZ){
4193 len += ucs4_strlen(travel->text);
4194 if (len >= HLSZ)
4195 break;
4198 * This comes after the break above because it will
4199 * be accounted for in the while loop below.
4201 if(!tentative_cur_l){
4202 if(tentative_p_ind <= ucs4_strlen(travel->text))
4203 tentative_cur_l = travel;
4204 else
4205 tentative_p_ind -= ucs4_strlen(travel->text);
4208 travel = travel->next;
4211 ods.cur_l = travel;
4212 ods.p_ind = ucs4_strlen(travel->text) - len + HLSZ;
4216 /* insert the remainder of text */
4217 if (i != FALSE && work_buf_len > 0) {
4218 ucs4_strncpy(&ods.cur_l->text[ods.p_ind], work_buf, HLSZ-ods.p_ind);
4219 ods.cur_l->text[HLSZ-1] = '\0';
4220 work_buf = work_buf_begin;
4221 free(work_buf);
4223 if(FormatLines(ods.cur_l, empty, LINEWID(),
4224 headents[ods.cur_e].break_on_comma, 0) == -1) {
4225 i = FALSE;
4226 } else {
4227 len = 0;
4228 travel = ods.cur_l;
4229 while (len < work_buf_len + ods.p_ind){
4230 if(!tentative_cur_l){
4231 if(tentative_p_ind <= ucs4_strlen(travel->text))
4232 tentative_cur_l = travel;
4233 else
4234 tentative_p_ind -= ucs4_strlen(travel->text);
4237 len += ucs4_strlen(travel->text);
4238 if (len >= work_buf_len + ods.p_ind)
4239 break;
4241 travel = travel->next;
4244 ods.cur_l = travel;
4245 ods.p_ind = ucs4_strlen(travel->text) - len + work_buf_len + ods.p_ind;
4246 if(tentative_cur_l
4247 && tentative_p_ind >= 0
4248 && tentative_p_ind <= ucs4_strlen(tentative_cur_l->text)){
4249 ods.cur_l = tentative_cur_l;
4250 ods.p_ind = tentative_p_ind;
4255 free(buf);
4256 return(i);
4262 * break_point - Break the given line at the most reasonable character breakch
4263 * within maxwid max characters.
4265 * returns:
4266 * Pointer to the best break point in s, or
4267 * Pointer to the beginning of s if no break point found
4269 UCS *
4270 break_point(UCS *line, int maxwid, UCS breakch, int *quotedarg)
4272 UCS *bp; /* break point */
4273 int quoted;
4276 * Start at maxwid and work back until first opportunity to break.
4278 bp = ucs4_particular_width(line, maxwid);
4281 * Quoted should be set up for the start of line. Since we want
4282 * to move to bp and work our way back we need to scan through the
4283 * line up to bp setting quoted appropriately.
4285 if(quotedarg)
4286 ucs4_strqchr(line, '\0', quotedarg, bp-line);
4288 quoted = quotedarg ? *quotedarg : 0;
4290 while(bp != line){
4291 if(breakch == ',' && *bp == '"') /* don't break on quoted ',' */
4292 quoted = !quoted; /* toggle quoted state */
4294 if(*bp == breakch && !quoted){
4295 if(breakch == ' '){
4296 if(ucs4_str_width_ptr_to_ptr(line, bp+1) < maxwid){
4297 bp++; /* leave the ' ' */
4298 break;
4301 else{
4303 * if break char isn't a space, leave a space after
4304 * the break char.
4306 if(!(ucs4_str_width_ptr_to_ptr(line, bp+1) >= maxwid
4307 || (bp[1] == ' ' && ucs4_str_width_ptr_to_ptr(line, bp+2) >= maxwid))){
4308 bp += (bp[1] == ' ') ? 2 : 1;
4309 break;
4314 bp--;
4317 if(quotedarg)
4318 *quotedarg = quoted;
4320 return((quoted) ? line : bp);
4327 * hldelete() - remove the header line pointed to by l from the linked list
4328 * of lines.
4330 * notes:
4331 * the case of first line in field is kind of bogus. since
4332 * the array of headers has a pointer to the first line, and
4333 * i don't want to worry about this too much, i just copied
4334 * the line below and removed it rather than the first one
4335 * from the list.
4337 * returns:
4338 * TRUE if it worked
4339 * FALSE otherwise
4342 hldelete(struct hdr_line *l)
4344 register struct hdr_line *lp;
4346 if(l == NULL)
4347 return(FALSE);
4349 if(l->next == NULL && l->prev == NULL){ /* only one line in field */
4350 l->text[0] = '\0';
4351 return(TRUE); /* no free only line in list */
4353 else if(l->next == NULL){ /* last line in field */
4354 l->prev->next = NULL;
4356 else if(l->prev == NULL){ /* first line in field */
4357 ucs4_strncpy(l->text, l->next->text, HLSZ);
4358 l->text[HLSZ-1] = '\0';
4359 lp = l->next;
4360 if((l->next = lp->next) != NULL)
4361 l->next->prev = l;
4362 l = lp;
4364 else{ /* some where in field */
4365 l->prev->next = l->next;
4366 l->next->prev = l->prev;
4369 l->next = NULL;
4370 l->prev = NULL;
4371 free((char *)l);
4372 return(TRUE);
4378 * is_blank - returns true if the next n chars from coordinates row, col
4379 * on display are spaces
4382 is_blank(int row, int col, int n)
4384 n += col;
4385 for( ;col < n; col++){
4386 if(pscr(row, col) == NULL || pscr(row, col)->c != ' ')
4387 return(0);
4389 return(1);
4394 * ShowPrompt - display key help corresponding to the current header entry
4396 void
4397 ShowPrompt(void)
4399 if(headents[ods.cur_e].key_label){
4400 menu_header[TO_KEY].name = "^T";
4401 menu_header[TO_KEY].label = headents[ods.cur_e].key_label;
4402 KS_OSDATASET(&menu_header[TO_KEY], KS_OSDATAGET(&headents[ods.cur_e]));
4404 else
4405 menu_header[TO_KEY].name = NULL;
4407 if(Pmaster && Pmaster->exit_label)
4408 menu_header[SEND_KEY].label = Pmaster->exit_label;
4409 else if(gmode & (MDVIEW | MDHDRONLY))
4410 menu_header[SEND_KEY].label = (gmode & MDHDRONLY) ? "eXit/Save" : "eXit";
4411 else
4412 menu_header[SEND_KEY].label = N_("Send");
4414 if(gmode & MDVIEW){
4415 menu_header[CUT_KEY].name = NULL;
4416 menu_header[DEL_KEY].name = NULL;
4417 menu_header[UDEL_KEY].name = NULL;
4419 else{
4420 menu_header[CUT_KEY].name = "^K";
4421 menu_header[DEL_KEY].name = "^D";
4422 menu_header[UDEL_KEY].name = "^U";
4425 if(Pmaster->ctrlr_label){
4426 menu_header[RICH_KEY].label = Pmaster->ctrlr_label;
4427 menu_header[RICH_KEY].name = "^R";
4429 else if(gmode & MDHDRONLY){
4430 menu_header[RICH_KEY].name = NULL;
4432 else{
4433 menu_header[RICH_KEY].label = N_("Rich Hdr");
4434 menu_header[RICH_KEY].name = "^R";
4437 if(gmode & MDHDRONLY){
4438 if(headents[ods.cur_e].fileedit){
4439 menu_header[PONE_KEY].name = "^_";
4440 menu_header[PONE_KEY].label = N_("Edit File");
4442 else
4443 menu_header[PONE_KEY].name = NULL;
4445 menu_header[ATT_KEY].name = NULL;
4447 else{
4448 menu_header[PONE_KEY].name = "^O";
4449 menu_header[PONE_KEY].label = N_("Postpone");
4451 menu_header[ATT_KEY].name = "^J";
4454 wkeyhelp(menu_header);
4459 * packheader - packup all of the header fields for return to caller.
4460 * NOTE: all of the header info passed in, including address
4461 * of the pointer to each string is contained in the
4462 * header entry array "headents".
4465 packheader(void)
4467 register int i = 0; /* array index */
4468 register int count; /* count of chars in a field */
4469 register int retval = TRUE;
4470 register char *bufp;
4471 register struct hdr_line *line;
4472 char *p;
4474 if(!headents)
4475 return(TRUE);
4477 while(headents[i].name != NULL){
4478 #ifdef ATTACHMENTS
4480 * attachments are special case, already in struct we pass back
4482 if(headents[i].is_attach){
4483 i++;
4484 continue;
4486 #endif
4488 if(headents[i].blank){
4489 i++;
4490 continue;
4494 * count chars to see if we need a new malloc'd space for our
4495 * array.
4497 line = headents[i].hd_text;
4498 count = 0;
4499 while(line != NULL){
4501 * add one for possible concatenation of a ' ' character ...
4503 p = ucs4_to_utf8_cpystr(line->text);
4504 if(p){
4505 count += strlen(p);
4506 if(p[0] && p[strlen(p)-1] == ',')
4507 count++;
4509 fs_give((void **) &p);
4512 line = line->next;
4515 line = headents[i].hd_text;
4516 if(count <= headents[i].maxlen){
4517 *headents[i].realaddr[0] = '\0';
4519 else{
4521 * don't forget to include space for the null terminator!!!!
4523 if((bufp = (char *)malloc((count+1) * sizeof(char))) != NULL){
4524 *bufp = '\0';
4526 free(*headents[i].realaddr);
4527 *headents[i].realaddr = bufp;
4528 headents[i].maxlen = count;
4530 else{
4531 emlwrite("Can't make room to pack header field.", NULL);
4532 retval = FALSE;
4536 if(retval != FALSE){
4537 int saw_current_line = 0;
4539 while(line != NULL){
4541 /* pass the cursor offset back in Pmaster struct */
4542 if(headents[i].start_here && Pmaster && !saw_current_line){
4543 if(ods.cur_l == line)
4544 saw_current_line++;
4545 else
4546 Pmaster->edit_offset += ucs4_strlen(line->text);
4549 p = ucs4_to_utf8_cpystr(line->text);
4550 if(p){
4551 strncat(*headents[i].realaddr, p, headents[i].maxlen+1-strlen(*headents[i].realaddr)-1);
4552 (*headents[i].realaddr)[headents[i].maxlen] = '\0';
4554 if(p[0] && p[strlen(p)-1] == ','){
4555 strncat(*headents[i].realaddr, " ", headents[i].maxlen+1-strlen(*headents[i].realaddr)-1);
4556 (*headents[i].realaddr)[headents[i].maxlen] = '\0';
4559 fs_give((void **) &p);
4562 line = line->next;
4566 i++;
4569 return(retval);
4575 * zotheader - free all malloc'd lines associated with the header structs
4577 void
4578 zotheader(void)
4580 register struct headerentry *i;
4582 for(i = headents; headents && i->name; i++)
4583 zotentry(i->hd_text);
4588 * zotentry - free malloc'd space associated with the given linked list
4590 void
4591 zotentry(struct hdr_line *l)
4593 register struct hdr_line *ld, *lf = l;
4595 while((ld = lf) != NULL){
4596 lf = ld->next;
4597 ld->next = ld->prev = NULL;
4598 free((char *) ld);
4605 * zotcomma - blast any trailing commas and white space from the end
4606 * of the given line
4609 zotcomma(UCS *s)
4611 UCS *p;
4612 int retval = FALSE;
4614 p = &s[ucs4_strlen(s)];
4615 while(--p >= s){
4616 if(*p != ' '){
4617 if(*p == ','){
4618 *p = '\0';
4619 retval = TRUE;
4622 return(retval);
4626 return(retval);
4631 * Save the current state of global variables so that we can restore
4632 * them later. This is so we can call pico again.
4633 * Also have to initialize some variables that normally would be set to
4634 * zero on startup.
4636 VARS_TO_SAVE *
4637 save_pico_state(void)
4639 VARS_TO_SAVE *ret;
4640 extern int vtrow;
4641 extern int vtcol;
4642 extern int lbound;
4643 extern VIDEO **vscreen;
4644 extern VIDEO **pscreen;
4645 extern int pico_all_done;
4646 extern jmp_buf finstate;
4647 extern UCS *pico_anchor;
4649 if((ret = (VARS_TO_SAVE *)malloc(sizeof(VARS_TO_SAVE))) == NULL)
4650 return(ret);
4652 ret->vtrow = vtrow;
4653 ret->vtcol = vtcol;
4654 ret->lbound = lbound;
4655 ret->vscreen = vscreen;
4656 ret->pscreen = pscreen;
4657 ret->ods = ods;
4658 ret->delim_ps = delim_ps;
4659 ret->invert_ps = invert_ps;
4660 ret->pico_all_done = pico_all_done;
4661 memcpy(ret->finstate, finstate, sizeof(jmp_buf));
4662 ret->pico_anchor = pico_anchor;
4663 ret->Pmaster = Pmaster;
4664 ret->fillcol = fillcol;
4665 if((ret->pat = (UCS *)malloc(sizeof(UCS) * (ucs4_strlen(pat)+1))) != NULL)
4666 ucs4_strncpy(ret->pat, pat, ucs4_strlen(pat)+1);
4668 ret->ComposerTopLine = ComposerTopLine;
4669 ret->ComposerEditing = ComposerEditing;
4670 ret->gmode = gmode;
4671 ret->alt_speller = alt_speller;
4672 ret->quote_str = glo_quote_str;
4673 ret->wordseps = glo_wordseps;
4674 ret->currow = currow;
4675 ret->curcol = curcol;
4676 ret->thisflag = thisflag;
4677 ret->lastflag = lastflag;
4678 ret->curgoal = curgoal;
4679 ret->opertree = (char *) malloc(sizeof(char) * (strlen(opertree) + 1));
4680 if(ret->opertree != NULL)
4681 strncpy(ret->opertree, opertree, strlen(opertree)+1);
4683 ret->curwp = curwp;
4684 ret->wheadp = wheadp;
4685 ret->curbp = curbp;
4686 ret->bheadp = bheadp;
4687 ret->km_popped = km_popped;
4688 ret->mrow = term.t_mrow;
4690 /* Initialize for next pico call */
4691 wheadp = NULL;
4692 curwp = NULL;
4693 bheadp = NULL;
4694 curbp = NULL;
4696 return(ret);
4700 void
4701 restore_pico_state(VARS_TO_SAVE *state)
4703 extern int vtrow;
4704 extern int vtcol;
4705 extern int lbound;
4706 extern VIDEO **vscreen;
4707 extern VIDEO **pscreen;
4708 extern int pico_all_done;
4709 extern jmp_buf finstate;
4710 extern UCS *pico_anchor;
4712 clearcursor();
4713 vtrow = state->vtrow;
4714 vtcol = state->vtcol;
4715 lbound = state->lbound;
4716 vscreen = state->vscreen;
4717 pscreen = state->pscreen;
4718 ods = state->ods;
4719 delim_ps = state->delim_ps;
4720 invert_ps = state->invert_ps;
4721 pico_all_done = state->pico_all_done;
4722 memcpy(finstate, state->finstate, sizeof(jmp_buf));
4723 pico_anchor = state->pico_anchor;
4724 Pmaster = state->Pmaster;
4725 if(Pmaster)
4726 headents = Pmaster->headents;
4728 fillcol = state->fillcol;
4729 if(state->pat)
4730 ucs4_strncpy(pat, state->pat, NPAT);
4732 ComposerTopLine = state->ComposerTopLine;
4733 ComposerEditing = state->ComposerEditing;
4734 gmode = state->gmode;
4735 alt_speller = state->alt_speller;
4736 glo_quote_str = state->quote_str;
4737 glo_wordseps = state->wordseps;
4738 currow = state->currow;
4739 curcol = state->curcol;
4740 thisflag = state->thisflag;
4741 lastflag = state->lastflag;
4742 curgoal = state->curgoal;
4743 if(state->opertree){
4744 strncpy(opertree, state->opertree, sizeof(opertree));
4745 opertree[sizeof(opertree)-1] = '\0';
4748 curwp = state->curwp;
4749 wheadp = state->wheadp;
4750 curbp = state->curbp;
4751 bheadp = state->bheadp;
4752 km_popped = state->km_popped;
4753 term.t_mrow = state->mrow;
4757 void
4758 free_pico_state(VARS_TO_SAVE *state)
4760 if(state->pat)
4761 free(state->pat);
4763 if(state->opertree)
4764 free(state->opertree);
4766 free(state);
4771 * Ok to call this twice in a row because it won't do anything the second
4772 * time.
4774 void
4775 fix_mangle_and_err(int *mangled, char **errmsg, char *name)
4777 if(mangled && *mangled){
4778 ttresize();
4779 picosigs();
4780 PaintBody(0);
4781 *mangled = 0;
4784 if(errmsg && *errmsg){
4785 if(**errmsg){
4786 char err[500];
4788 snprintf(err, sizeof(err), "%s field: %s", name, *errmsg);
4789 (*term.t_beep)();
4790 emlwrite(err, NULL);
4792 else
4793 mlerase();
4795 free(*errmsg);
4796 *errmsg = NULL;
4802 * What is this for?
4803 * This is so that the To line will be appended to by an Lcc
4804 * entry unless the user types in the To line after the Lcc
4805 * has already been set.
4807 void
4808 mark_sticky(struct headerentry *h)
4810 if(h && (!h->sticky_special || h->bldr_private))
4811 h->sticky = 1;
4815 #ifdef MOUSE
4816 #undef HeaderEditor
4819 * Wrapper function for the real header editor.
4820 * Does the important tasks of:
4821 * 1) verifying that we _can_ edit the headers.
4822 * 2) acting on the result code from the header editor.
4825 HeaderEditor(int f, int n)
4827 int retval;
4830 #ifdef _WINDOWS
4831 /* Sometimes we get here from a scroll callback, which
4832 * is no good at all because mswin is not ready to process input and
4833 * this _headeredit() will never do anything.
4834 * Putting this test here was the most general solution I could think
4835 * of. */
4836 if (!mswin_caninput())
4837 return (-1);
4838 #endif
4840 retval = HeaderEditorWork(f, n);
4841 if (retval == -3) {
4842 retval = mousepress(0,0);
4844 return (retval);
4846 #endif