#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: composer.c 7281 2004-04-05 01:06:15Z amk $"; #endif /* * Program: Pine composer routines * * * Michael Seibel * Networks and Distributed Computing * Computing and Communications * University of Washington * Administration Builiding, AG-44 * Seattle, Washington, 98195, USA * Internet: mikes@cac.washington.edu * * Please address all bugs and comments to "pine-bugs@cac.washington.edu" * * * Pine and Pico are registered trademarks of the University of Washington. * No commercial use of these trademarks may be made without prior written * permission of the University of Washington. * * Pine, Pico, and Pilot software and its included text are Copyright * 1989-2001 by the University of Washington. * * The full text of our legal notices is contained in the file called * CPYRIGHT, included with this distribution. * * * NOTES: * * - composer.c is the composer for the PINE mail system * * - tabled 01/19/90 * * Notes: These routines aren't incorporated yet, because the composer as * a whole still needs development. These are ideas that should * be implemented in later releases of PINE. See the notes in * pico.c concerning what needs to be done .... * * - untabled 01/30/90 * * Notes: Installed header line editing, with wrapping and unwrapping, * context sensitive help, and other mail header editing features. * * - finalish code cleanup 07/15/91 * * Notes: Killed/yanked header lines use emacs kill buffer. * Arbitrarily large headers are handled gracefully. * All formatting handled by FormatLines. * * - Work done to optimize display painting 06/26/92 * Not as clean as it should be, needs more thought * */ #include "headers.h" int InitEntryText PROTO((char *, struct headerentry *)); int HeaderOffset PROTO((int)); int HeaderFocus PROTO((int, int)); int LineEdit PROTO((int)); int header_downline PROTO((int, int)); int header_upline PROTO((int)); int FormatLines PROTO((struct hdr_line *, char *, int, int, int)); char *strqchr PROTO((char *, int, int *, int)); int ComposerHelp PROTO((int)); void NewTop PROTO((int)); void display_delimiter PROTO((int)); void zotentry PROTO((struct hdr_line *)); int InvertPrompt PROTO((int, int)); int partial_entries PROTO((void)); int physical_line PROTO((struct hdr_line *)); int strend PROTO((char *, int)); int KillHeaderLine PROTO((struct hdr_line *, int)); int SaveHeaderLines PROTO((void)); char *break_point PROTO((char *, int, int, int *)); int hldelete PROTO((struct hdr_line *)); int is_blank PROTO((int, int, int)); int zotcomma PROTO((char *)); struct hdr_line *first_hline PROTO((int *)); struct hdr_line *first_sel_hline PROTO((int *)); struct hdr_line *next_hline PROTO((int *, struct hdr_line *)); struct hdr_line *next_sel_hline PROTO((int *, struct hdr_line *)); struct hdr_line *prev_hline PROTO((int *, struct hdr_line *)); struct hdr_line *prev_sel_hline PROTO((int *, struct hdr_line *)); struct hdr_line *first_requested_hline PROTO((int *)); void fix_mangle_and_err PROTO((int *, char **, char *)); /* * definition header field array, structures defined in pico.h */ struct headerentry *headents; /* * structure that keeps track of the range of header lines that are * to be displayed and other fun stuff about the header */ struct on_display ods; /* global on_display struct */ /* * useful macros */ #define HALLOC() (struct hdr_line *)malloc(sizeof(struct hdr_line)) #define LINELEN() (term.t_ncol - headents[ods.cur_e].prlen) #define BOTTOM() (term.t_nrow - term.t_mrow) #define FULL_SCR() (BOTTOM() - 3) #define HALF_SCR() (FULL_SCR()/2) #ifdef MOUSE /* * Redefine HeaderEditor to install wrapper required for mouse even * handling... */ #define HeaderEditor HeaderEditorWork #endif /* MOUSE */ #if (defined(DOS) && !defined(_WINDOWS)) || defined(OS2) #define HDR_DELIM "\xCD\xCD\xCD\xCD\xCD Message Text \xCD\xCD\xCD\xCD\xCD" #else #define HDR_DELIM "----- Message Text -----" #endif /* * useful declarations */ static short delim_ps = 0; /* previous state */ static short invert_ps = 0; /* previous state */ static KEYMENU menu_header[] = { {"^G", "Get Help", KS_SCREENHELP}, {"^X", "Send", KS_SEND}, {"^R", "Rich Hdr", KS_RICHHDR}, {"^Y", "PrvPg/Top", KS_PREVPAGE}, {"^K", "Cut Line", KS_CURPOSITION}, {"^O", "Postpone", KS_POSTPONE}, {"^C", "Cancel", KS_CANCEL}, {"^D", "Del Char", KS_NONE}, {"^J", "Attach", KS_ATTACH}, {"^V", "NxtPg/End", KS_NEXTPAGE}, {"^U", "UnDel Line", KS_NONE}, {NULL, NULL} }; #define SEND_KEY 1 #define RICH_KEY 2 #define CUT_KEY 4 #define PONE_KEY 5 #define DEL_KEY 7 #define ATT_KEY 8 #define UDEL_KEY 10 #define TO_KEY 11 /* * function key mappings for header editor */ static int ckm[12][2] = { { F1, (CTRL|'G')}, { F2, (CTRL|'C')}, { F3, (CTRL|'X')}, { F4, (CTRL|'D')}, { F5, (CTRL|'R')}, { F6, (CTRL|'J')}, { F7, 0 }, { F8, 0 }, { F9, (CTRL|'K')}, { F10, (CTRL|'U')}, { F11, (CTRL|'O')}, { F12, (CTRL|'T')} }; /* * InitMailHeader - initialize header array, and set beginning editor row * range. The header entry structure should look just like * what is written on the screen, the vector * (entry, line, offset) will describe the current cursor * position in the header. * * Returns: TRUE if special header handling was requested, * FALSE under standard default behavior. */ InitMailHeader(mp) PICO *mp; { char *addrbuf; struct headerentry *he; int rv; if(!mp->headents){ headents = NULL; return(FALSE); } /* * initialize some of on_display structure, others below... */ ods.p_off = 0; ods.p_line = COMPOSER_TOP_LINE; ods.top_l = ods.cur_l = NULL; headents = mp->headents; /*--- initialize the fields in the headerent structure ----*/ for(he = headents; he->name != NULL; he++){ he->hd_text = NULL; he->display_it = he->display_it ? he->display_it : !he->rich_header; if(he->is_attach) { /*--- A lot of work to do since attachments are special ---*/ he->maxlen = 0; if(mp->attachments != NULL){ char buf[NLINE]; int attno = 0; int l1, ofp, ofp1, ofp2; /* OverFlowProtection */ size_t addrbuflen = 4 * NLINE; /* malloc()ed size of addrbuf */ PATMT *ap = mp->attachments; ofp = NLINE - 35; addrbuf = (char *)malloc(addrbuflen); addrbuf[0] = '\0'; buf[0] = '\0'; while(ap){ if((l1 = strlen(ap->filename)) <= ofp){ ofp1 = l1; ofp2 = ofp - l1; } else{ ofp1 = ofp; ofp2 = 0; } if(ap->filename){ sprintf(buf, "%d. %.*s%s %s%s%s\"", ++attno, ofp1, ap->filename, (l1 > ofp) ? "...]" : "", ap->size ? "(" : "", ap->size ? ap->size : "", ap->size ? ") " : ""); /* append description, escaping quotes */ if(ap->description){ char *dp = ap->description, *bufp = &buf[strlen(buf)]; int escaped = 0; do if(*dp == '\"' && !escaped){ *bufp++ = '\\'; ofp2--; } else if(escaped){ *bufp++ = '\\'; escaped = 0; } while(--ofp2 > 0 && (*bufp++ = *dp++)); *bufp = '\0'; } sprintf(buf + strlen(buf), "\"%s", ap->next ? "," : ""); if(strlen(addrbuf) + strlen(buf) >= addrbuflen){ addrbuflen += NLINE * 4; if(!(addrbuf = (char *)realloc(addrbuf, addrbuflen))){ emlwrite("\007Can't realloc addrbuf to %d bytes", (void *) addrbuflen); return(ABORT); } } strcat(addrbuf, buf); } ap = ap->next; } InitEntryText(addrbuf, he); free((char *)addrbuf); } else { InitEntryText("", he); } he->realaddr = NULL; } else { if(!he->blank) addrbuf = *(he->realaddr); else addrbuf = ""; InitEntryText(addrbuf, he); } } /* * finish initialization and then figure out display layout. * first, look for any field the caller requested we start in, * and set the offset into that field. */ if(ods.cur_l = first_requested_hline(&ods.cur_e)){ ods.top_e = 0; /* init top_e */ ods.top_l = first_hline(&ods.top_e); if(!HeaderFocus(ods.cur_e, Pmaster ? Pmaster->edit_offset : 0)) HeaderFocus(ods.cur_e, 0); rv = TRUE; } else{ ods.top_l = first_hline(&ods.cur_e); ods.cur_l = first_sel_hline(&ods.cur_e); ods.top_e = ods.cur_e; rv = 0; } UpdateHeader(0); return(rv); } /* * InitEntryText - Add the given header text into the header entry * line structure. */ InitEntryText(address, e) char *address; struct headerentry *e; { struct hdr_line *curline; register int longest; /* * get first chunk of memory, and tie it to structure... */ if((curline = HALLOC()) == NULL){ emlwrite("Unable to make room for full Header.", NULL); return(FALSE); } longest = term.t_ncol - e->prlen - 1; curline->text[0] = '\0'; curline->next = NULL; curline->prev = NULL; e->hd_text = curline; /* tie it into the list */ if(FormatLines(curline, address, longest, e->break_on_comma, 0) == -1) return(FALSE); else return(TRUE); } /* * ResizeHeader - Handle resizing display when SIGWINCH received. * * notes: * works OK, but needs thorough testing * */ ResizeHeader() { register struct headerentry *i; register int offset; if(!headents) return(TRUE); offset = (ComposerEditing) ? HeaderOffset(ods.cur_e) : 0; for(i=headents; i->name; i++){ /* format each entry */ if(FormatLines(i->hd_text, "", (term.t_ncol - i->prlen), i->break_on_comma, 0) == -1){ return(-1); } } if(ComposerEditing) /* restart at the top */ HeaderFocus(ods.cur_e, offset); /* fix cur_l and p_off */ else { struct hdr_line *l; int e; for(i = headents; i->name != NULL; i++); /* Find last line */ i--; e = i - headents; l = headents[e].hd_text; if(!headents[e].display_it || headents[e].blank) l = prev_sel_hline(&e, l); /* last selectable line */ if(!l){ e = i - headents; l = headents[e].hd_text; } HeaderFocus(e, -1); /* put focus on last line */ } if(ComposerTopLine != COMPOSER_TOP_LINE) UpdateHeader(0); PaintBody(0); if(ComposerEditing) movecursor(ods.p_line, ods.p_off+headents[ods.cur_e].prlen); (*term.t_flush)(); return(TRUE); } /* * HeaderOffset - return the character offset into the given header */ HeaderOffset(h) int h; { register struct hdr_line *l; int i = 0; l = headents[h].hd_text; while(l != ods.cur_l){ i += strlen(l->text); l = l->next; } return(i+ods.p_off); } /* * HeaderFocus - put the dot at the given offset into the given header */ HeaderFocus(h, offset) int h, offset; { register struct hdr_line *l; register int i; int last = 0; if(offset == -1) /* focus on last line */ last = 1; l = headents[h].hd_text; while(1){ if(last && l->next == NULL){ break; } else{ if((i=strlen(l->text)) >= offset) break; else offset -= i; } if((l = l->next) == NULL) return(FALSE); } ods.cur_l = l; ods.p_len = strlen(l->text); ods.p_off = (last) ? 0 : offset; return(TRUE); } /* * HeaderEditor() - edit the mail header field by field, trapping * important key sequences, hand the hard work off * to LineEdit(). * returns: * -3 if we drop out bottom *and* want to process mouse event * -1 if we drop out the bottom * FALSE if editing is cancelled * TRUE if editing is finished */ HeaderEditor(f, n) int f, n; { register int i; register int ch; register char *bufp; struct headerentry *h; int cur_e, mangled, retval = -1, hdr_only = (gmode & MDHDRONLY) ? 1 : 0; char *errmss, *err; #ifdef ATTACHMENTS register int status; /* return status of something*/ #endif #ifdef MOUSE MOUSEPRESS mp; #endif if(!headents) return(TRUE); /* nothing to edit! */ ComposerEditing = TRUE; display_delimiter(0); /* provide feedback */ #ifdef _WINDOWS mswin_setscrollrange (0, 0); #endif /* _WINDOWS */ /* * Decide where to begin editing. if f == TRUE begin editing * at the bottom. this case results from the cursor backing * into the editor from the bottom. otherwise, the user explicitly * requested editing the header, and we begin at the top. * * further, if f == 1, we moved into the header by hitting the up arrow * in the message text, else if f == 2, we moved into the header by * moving past the left edge of the top line in the message. so, make * the end of the last line of the last entry the current cursor position * lastly, if f == 3, we moved into the header by hitting backpage() on * the top line of the message, so scroll a page back. */ if(f){ if(f == 2){ /* 2 leaves cursor at end */ struct hdr_line *l = ods.cur_l; int e = ods.cur_e; /*--- make sure on last field ---*/ while(l = next_sel_hline(&e, l)) if(headents[ods.cur_e].display_it){ ods.cur_l = l; ods.cur_e = e; } ods.p_off = 1000; /* and make sure at EOL */ } else{ /* * note: assumes that ods.cur_e and ods.cur_l haven't changed * since we left... */ /* fix postition */ if(curwp->w_doto < headents[ods.cur_e].prlen) ods.p_off = 0; else if(curwp->w_doto < ods.p_off + headents[ods.cur_e].prlen) ods.p_off = curwp->w_doto - headents[ods.cur_e].prlen; else ods.p_off = 1000; /* and scroll back if needed */ if(f == 3) for(i = 0; header_upline(0) && i <= FULL_SCR(); i++) ; } ods.p_line = ComposerTopLine - 2; } /* else just trust what ods contains */ InvertPrompt(ods.cur_e, TRUE); /* highlight header field */ sgarbk = 1; do{ if(km_popped){ km_popped--; if(km_popped == 0) sgarbk = 1; } if(sgarbk){ if(km_popped){ /* temporarily change to cause menu to paint */ term.t_mrow = 2; curwp->w_ntrows -= 2; movecursor(term.t_nrow-2, 0); /* clear status line, too */ peeol(); } else if(term.t_mrow == 0) PaintBody(1); ShowPrompt(); /* display correct options */ sgarbk = 0; if(km_popped){ term.t_mrow = 0; curwp->w_ntrows += 2; } } ch = LineEdit(!(gmode&MDVIEW)); /* work on the current line */ if(km_popped) switch(ch){ case NODATA: case (CTRL|'L'): km_popped++; break; default: movecursor(term.t_nrow-2, 0); peeol(); movecursor(term.t_nrow-1, 0); peeol(); movecursor(term.t_nrow, 0); peeol(); break; } switch (ch){ case (CTRL|'R') : /* Toggle header display */ if(Pmaster->pine_flags & MDHDRONLY){ if(Pmaster->expander){ packheader(); call_expander(); PaintBody(0); break; } else goto bleep; } /*---- Are there any headers to expand above us? ---*/ for(h = headents; h != &headents[ods.cur_e]; h++) if(h->rich_header) break; if(h->rich_header) InvertPrompt(ods.cur_e, FALSE); /* Yes, don't leave inverted */ mangled = 0; err = NULL; if(partial_entries()){ /*--- Just turned off all rich headers --*/ if(headents[ods.cur_e].rich_header){ /*-- current header got turned off too --*/ if(headents[ods.cur_e].builder) /* verify text */ i = call_builder(&headents[ods.cur_e], &mangled, &err)>0; /* Check below */ for(cur_e =ods.cur_e; headents[cur_e].name!=NULL; cur_e++) if(!headents[cur_e].rich_header) break; if(headents[cur_e].name == NULL) { /* didn't find one, check above */ for(cur_e =ods.cur_e; headents[cur_e].name!=NULL; cur_e--) if(!headents[cur_e].rich_header) break; } ods.p_off = 0; ods.cur_e = cur_e; ods.cur_l = headents[ods.cur_e].hd_text; } } ods.p_line = 0; /* force update */ UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); break; case (CTRL|'C') : /* bag whole thing ?*/ if(abort_composer(1, 0) == TRUE) return(FALSE); break; case (CTRL|'X') : /* Done. Send it. */ i = 0; #ifdef ATTACHMENTS if(headents[ods.cur_e].is_attach){ /* verify the attachments, and pretty things up in case * we come back to the composer due to error... */ if((i = SyncAttach()) != 0){ sleep(2); /* give time for error to absorb */ FormatLines(headents[ods.cur_e].hd_text, "", term.t_ncol - headents[ods.cur_e].prlen, headents[ods.cur_e].break_on_comma, 0); } } else #endif mangled = 0; err = NULL; if(headents[ods.cur_e].builder) /* verify text? */ i = call_builder(&headents[ods.cur_e], &mangled, &err); if(i < 0){ /* don't leave without a valid addr */ fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); break; } else if(i > 0){ ods.cur_l = headents[ods.cur_e].hd_text; /* attach cur_l */ ods.p_off = 0; ods.p_line = 0; /* force realignment */ fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); NewTop(0); } fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); if(wquit(1,0) == TRUE) return(TRUE); if(i > 0){ /* * need to be careful here because pointers might be messed up. * also, could do a better job of finding the right place to * put the dot back (i.e., the addr/list that was expanded). */ UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); } break; case (CTRL|'Z') : /* Suspend compose */ if(gmode&MDSSPD){ /* is it allowed? */ bktoshell(); PaintBody(0); } else{ (*term.t_beep)(); emlwrite("Unknown Command: ^Z", NULL); } break; case (CTRL|'O') : /* Suspend message */ if(Pmaster->pine_flags & MDHDRONLY) goto bleep; i = 0; mangled = 0; err = NULL; if(headents[ods.cur_e].is_attach){ if(SyncAttach() < 0){ if(mlyesno("Problem with attachments. Postpone anyway?", FALSE) != TRUE){ if(FormatLines(headents[ods.cur_e].hd_text, "", term.t_ncol - headents[ods.cur_e].prlen, headents[ods.cur_e].break_on_comma, 0) == -1) emlwrite("\007Format lines failed!", NULL); UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); continue; } } } else if(headents[ods.cur_e].builder) i = call_builder(&headents[ods.cur_e], &mangled, &err); ods.cur_l = headents[ods.cur_e].hd_text; fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); if(i < 0) /* don't leave without a valid addr */ break; suspend_composer(1, 0); return(TRUE); #ifdef ATTACHMENTS case (CTRL|'J') : /* handle attachments */ if(Pmaster->pine_flags & MDHDRONLY) goto bleep; { char fn[NLINE], sz[32], cmt[NLINE]; int saved_km_popped; /* * Attachment questions mess with km_popped and assume * it is zero to start with. If we don't set it to zero * on entry, the message about mime type will be erased * by PaintBody. If we don't reset it when we come back, * the bottom three lines may be messed up. */ saved_km_popped = km_popped; km_popped = 0; if(AskAttach(fn, sz, cmt)){ status = !AppendAttachment(fn, sz, cmt); } km_popped = saved_km_popped; sgarbk = 1; /* clean up prompt */ } break; #endif case (CTRL|'I') : /* tab */ ods.p_off = 0; /* fall through... */ case (CTRL|'N') : case KEY_DOWN : header_downline(!hdr_only, hdr_only); break; case (CTRL|'P') : case KEY_UP : header_upline(1); break; case (CTRL|'V') : /* down a page */ case KEY_PGDN : cur_e = ods.cur_e; if(!next_sel_hline(&cur_e, ods.cur_l)){ header_downline(!hdr_only, hdr_only); if(!(gmode & MDHDRONLY)) retval = -1; /* tell caller we fell out */ } else{ int move_down, bot_pline; struct hdr_line *new_cur_l, *line, *next_line, *prev_line; move_down = BOTTOM() - 2 - ods.p_line; if(move_down < 0) move_down = 0; /* * Count down move_down lines to find the pointer to the line * that we want to become the current line. */ new_cur_l = ods.cur_l; cur_e = ods.cur_e; for(i = 0; i < move_down; i++){ next_line = next_hline(&cur_e, new_cur_l); if(!next_line) break; new_cur_l = next_line; } if(headents[cur_e].blank){ next_line = next_sel_hline(&cur_e, new_cur_l); if(!next_line) break; new_cur_l = next_line; } /* * Now call header_downline until we get down to the * new current line, so that the builders all get called. * New_cur_l will remain valid since we won't be calling * a builder for it during this loop. */ while(ods.cur_l != new_cur_l && header_downline(0, 0)) ; /* * Count back up, if we're at the bottom, to find the new * top line. */ cur_e = ods.cur_e; if(next_hline(&cur_e, ods.cur_l) == NULL){ /* * Cursor stops at bottom of headers, which is where * we are right now. Put as much of headers on * screen as will fit. Count up to figure * out which line is top_l and which p_line cursor is on. */ cur_e = ods.cur_e; line = ods.cur_l; /* leave delimiter on screen, too */ bot_pline = BOTTOM() - 1 - ((gmode & MDHDRONLY) ? 0 : 1); for(i = COMPOSER_TOP_LINE; i < bot_pline; i++){ prev_line = prev_hline(&cur_e, line); if(!prev_line) break; line = prev_line; } ods.top_l = line; ods.top_e = cur_e; ods.p_line = i; } else{ ods.top_l = ods.cur_l; ods.top_e = ods.cur_e; /* * We don't want to scroll down further than the * delimiter, so check to see if that is the case. * If it is, we move the p_line down the screen * until the bottom line is where we want it. */ bot_pline = BOTTOM() - 1 - ((gmode & MDHDRONLY) ? 0 : 1); cur_e = ods.cur_e; line = ods.cur_l; for(i = bot_pline; i > COMPOSER_TOP_LINE; i--){ next_line = next_hline(&cur_e, line); if(!next_line) break; line = next_line; } /* * i is the desired value of p_line. * If it is greater than COMPOSER_TOP_LINE, then * we need to adjust top_l. */ ods.p_line = i; line = ods.top_l; cur_e = ods.top_e; for(; i > COMPOSER_TOP_LINE; i--){ prev_line = prev_hline(&cur_e, line); if(!prev_line) break; line = prev_line; } ods.top_l = line; ods.top_e = cur_e; /* * Special case. If p_line is within one of the bottom, * move it to the bottom. */ if(ods.p_line == bot_pline - 1){ header_downline(0, 0); /* but put these back where we want them */ ods.p_line = bot_pline; ods.top_l = line; ods.top_e = cur_e; } } UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); } break; case (CTRL|'Y') : /* up a page */ case KEY_PGUP : for(i = 0; header_upline(0) && i <= FULL_SCR(); i++) if(i < 0) break; break; #ifdef MOUSE case KEY_MOUSE: mouse_get_last (NULL, &mp); switch(mp.button){ case M_BUTTON_LEFT : if (!mp.doubleclick) { if (mp.row < ods.p_line) { for (i = ods.p_line - mp.row; i > 0 && header_upline(0); --i) ; } else { for (i = mp.row-ods.p_line; i > 0 && header_downline(!hdr_only, 0); --i) ; } if((ods.p_off = mp.col - headents[ods.cur_e].prlen) <= 0) ods.p_off = 0; /* -3 is returned if we drop out bottom * *and* want to process a mousepress. The Headereditor * wrapper should make sense of this return code. */ if (ods.p_line >= ComposerTopLine) retval = -3; } break; case M_BUTTON_RIGHT : #ifdef _WINDOWS pico_popup(); #endif case M_BUTTON_MIDDLE : default : /* NOOP */ break; } break; #endif /* MOUSE */ case (CTRL|'T') : /* Call field selector */ errmss = NULL; if(headents[ods.cur_e].is_attach) { /*--- selector for attachments ----*/ char dir[NLINE], fn[NLINE], sz[NLINE]; strcpy(dir, (gmode & MDCURDIR) ? "." : ((gmode & MDTREE) || opertree[0]) ? opertree : gethomedir(NULL)); fn[0] = '\0'; if(FileBrowse(dir, NLINE, fn, NLINE, sz, FB_READ | FB_ATTACH) == 1){ /* got a new file */ char buf[NLINE]; sprintf(buf, "%s%c%s", dir, C_FILESEP, fn); (void) QuoteAttach(buf); sprintf(buf + strlen(buf), " (%s) \"\"%s", sz, (!headents[ods.cur_e].hd_text->text[0]) ? "":","); if(FormatLines(headents[ods.cur_e].hd_text, buf, term.t_ncol - headents[ods.cur_e].prlen, headents[ods.cur_e].break_on_comma,0)==-1){ emlwrite("\007Format lines failed!", NULL); } UpdateHeader(0); } /* else, nothing of interest */ } else if (headents[ods.cur_e].selector != NULL) { VARS_TO_SAVE *saved_state; /*---- General selector for non-attachments -----*/ /* * Since the selector may make a new call back to pico() * we need to save and restore the pico state here. */ if((saved_state = save_pico_state()) != NULL){ bufp = (*(headents[ods.cur_e].selector))(&errmss); restore_pico_state(saved_state); free_pico_state(saved_state); ttresize(); /* fixup screen bufs */ picosigs(); /* restore altered signals */ } else{ char *s = "Not enough memory"; errmss = (char *)malloc((strlen(s)+1) * sizeof(char)); strcpy(errmss, s); bufp = NULL; } if(bufp != NULL) { mangled = 0; err = NULL; if(headents[ods.cur_e].break_on_comma) { /*--- Must be an address ---*/ if(ods.cur_l->text[0] != '\0'){ struct hdr_line *h, *hh; int q = 0; /*--- Check for continuation of previous line ---*/ for(hh = h = headents[ods.cur_e].hd_text; h && h->next != ods.cur_l; h = h->next){ if(strqchr(h->text, ',', &q, -1)){ hh = h->next; } } if(hh && hh != ods.cur_l){ /*--- Looks like a continuation ---*/ ods.cur_l = hh; ods.p_len = strlen(hh->text); } for(i = ++ods.p_len; i; i--) ods.cur_l->text[i] = ods.cur_l->text[i-1]; ods.cur_l->text[0] = ','; } if(FormatLines(ods.cur_l, bufp, (term.t_ncol-headents[ods.cur_e].prlen), headents[ods.cur_e].break_on_comma, 0) == -1){ emlwrite("Problem adding address to header !", NULL); (*term.t_beep)(); break; } /* * If the "selector" has a "builder" as well, pass * what was just selected thru the builder... */ if(headents[ods.cur_e].builder){ struct hdr_line *l; int cur_row, top_too = 0; for(l = headents[ods.cur_e].hd_text, cur_row = 0; l && l != ods.cur_l; l = l->next, cur_row++) ; top_too = headents[ods.cur_e].hd_text == ods.top_l; if(call_builder(&headents[ods.cur_e], &mangled, &err) < 0){ fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); } for(ods.cur_l = headents[ods.cur_e].hd_text; ods.cur_l->next && cur_row; ods.cur_l = ods.cur_l->next, cur_row--) ; if(top_too) ods.top_l = headents[ods.cur_e].hd_text; } UpdateHeader(0); } else { strcpy(headents[ods.cur_e].hd_text->text, bufp); } free(bufp); /* mark this entry dirty */ headents[ods.cur_e].sticky = 1; headents[ods.cur_e].dirty = 1; fix_mangle_and_err(&mangled,&err,headents[ods.cur_e].name); } } else { /*----- No selector -----*/ (*term.t_beep)(); continue; } PaintBody(0); if(errmss != NULL) { (*term.t_beep)(); emlwrite(errmss, NULL); free(errmss); errmss = NULL; } continue; case (CTRL|'G'): /* HELP */ if(term.t_mrow == 0){ if(km_popped == 0){ km_popped = 2; sgarbk = 1; /* bring up menu */ break; } } if(!ComposerHelp(ods.cur_e)) break; /* else, fall through... */ case (CTRL|'L'): /* redraw requested */ PaintBody(0); break; case (CTRL|'_'): /* file editor */ if(headents[ods.cur_e].fileedit != NULL){ struct headerentry *e; struct hdr_line *line; int sz = 0; char *filename; VARS_TO_SAVE *saved_state; /* * Since the fileedit will make a new call back to pico() * we need to save and restore the pico state here. */ if((saved_state = save_pico_state()) != NULL){ e = &headents[ods.cur_e]; for(line = e->hd_text; line != NULL; line = line->next) sz += strlen(line->text); filename = (char *)malloc(sz + 1); filename[0] = '\0'; for(line = e->hd_text; line != NULL; line = line->next) strcat(filename, line->text); errmss = (*(headents[ods.cur_e].fileedit))(filename); free(filename); restore_pico_state(saved_state); free_pico_state(saved_state); ttresize(); /* fixup screen bufs */ picosigs(); /* restore altered signals */ } else{ char *s = "Not enough memory"; errmss = (char *)malloc((strlen(s)+1) * sizeof(char)); strcpy(errmss, s); } PaintBody(0); if(errmss != NULL) { (*term.t_beep)(); emlwrite(errmss, NULL); free(errmss); errmss = NULL; } continue; } else goto bleep; break; default : /* huh? */ bleep: if(ch&CTRL) emlwrite("\007Unknown command: ^%c", (void *)(ch&0xff)); else case BADESC: emlwrite("\007Unknown command", NULL); case NODATA: break; } } while (ods.p_line < ComposerTopLine); display_delimiter(1); curwp->w_flag |= WFMODE; movecursor(currow, curcol); ComposerEditing = FALSE; if (ComposerTopLine == BOTTOM()){ UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); } return(retval); } /* * */ int header_downline(beyond, gripe) int beyond, gripe; { struct hdr_line *new_l, *l; int new_e, status, fullpaint, len, e, incr = 0; /* calculate the next line: physical *and* logical */ status = 0; new_e = ods.cur_e; if((new_l = next_sel_hline(&new_e, ods.cur_l)) == NULL && !beyond){ if(gripe){ char xx[81]; strcpy(xx, "Can't move down. Use ^X to "); strcat(xx, (Pmaster && Pmaster->exit_label) ? Pmaster->exit_label : (gmode & MDHDRONLY) ? "eXit/Save" : (gmode & MDVIEW) ? "eXit" : "Send"); strcat(xx, "."); emlwrite(xx, NULL); } return(0); } /* * Because of blank header lines the cursor may need to move down * more than one line. Figure out how far. */ e = ods.cur_e; l = ods.cur_l; while(l != new_l){ if((l = next_hline(&e, l)) != NULL) incr++; else break; /* can't happen */ } ods.p_line += incr; fullpaint = ods.p_line >= BOTTOM(); /* force full redraw? */ /* expand what needs expanding */ if(new_e != ods.cur_e || !new_l){ /* new (or last) field ! */ if(new_l) InvertPrompt(ods.cur_e, FALSE); /* turn off current entry */ if(headents[ods.cur_e].is_attach) { /* verify data ? */ if(status = SyncAttach()){ /* fixup if 1 or -1 */ headents[ods.cur_e].rich_header = 0; if(FormatLines(headents[ods.cur_e].hd_text, "", term.t_ncol-headents[new_e].prlen, headents[ods.cur_e].break_on_comma, 0) == -1) emlwrite("\007Format lines failed!", NULL); } } else if(headents[ods.cur_e].builder) { /* expand addresses */ int mangled = 0; char *err = NULL; if((status = call_builder(&headents[ods.cur_e], &mangled, &err))>0){ struct hdr_line *l; /* fixup ods.cur_l */ ods.p_line = 0; /* force top line recalc */ for(l = headents[ods.cur_e].hd_text; l; l = l->next) ods.cur_l = l; if(new_l) /* if new_l, force validity */ new_l = headents[new_e].hd_text; NewTop(0); /* get new top_l */ } else if(status < 0){ /* bad addr? no leave! */ --ods.p_line; fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); InvertPrompt(ods.cur_e, TRUE); return(0); } fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); } if(new_l){ /* if one below, turn it on */ InvertPrompt(new_e, TRUE); sgarbk = 1; /* paint keymenu too */ } } if(new_l){ /* fixup new pointers */ ods.cur_l = (ods.cur_e != new_e) ? headents[new_e].hd_text : new_l; ods.cur_e = new_e; if(ods.p_off > (len = strlen(ods.cur_l->text))) ods.p_off = len; } if(!new_l || status || fullpaint){ /* handle big screen paint */ UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); if(!new_l){ /* make sure we're done */ ods.p_line = ComposerTopLine; InvertPrompt(ods.cur_e, FALSE); /* turn off current entry */ } } return(new_l ? 1 : 0); } /* * */ int header_upline(gripe) int gripe; { struct hdr_line *new_l, *l; int new_e, status, fullpaint, len, e, incr = 0; /* calculate the next line: physical *and* logical */ status = 0; new_e = ods.cur_e; if(!(new_l = prev_sel_hline(&new_e, ods.cur_l))){ /* all the way up! */ ods.p_line = COMPOSER_TOP_LINE; if(gripe) emlwrite("Can't move beyond top of %s", (Pmaster->pine_flags & MDHDRONLY) ? "entry" : "header"); return(0); } /* * Because of blank header lines the cursor may need to move up * more than one line. Figure out how far. */ e = ods.cur_e; l = ods.cur_l; while(l != new_l){ if((l = prev_hline(&e, l)) != NULL) incr++; else break; /* can't happen */ } ods.p_line -= incr; fullpaint = ods.p_line <= COMPOSER_TOP_LINE; if(new_e != ods.cur_e){ /* new field ! */ InvertPrompt(ods.cur_e, FALSE); if(headents[ods.cur_e].is_attach){ if(status = SyncAttach()){ /* non-zero ? reformat field */ headents[ods.cur_e].rich_header = 0; if(FormatLines(headents[ods.cur_e].hd_text, "", term.t_ncol - headents[ods.cur_e].prlen, headents[ods.cur_e].break_on_comma,0) == -1) emlwrite("\007Format lines failed!", NULL); } } else if(headents[ods.cur_e].builder){ int mangled = 0; char *err = NULL; if((status = call_builder(&headents[ods.cur_e], &mangled, &err)) >= 0){ /* repair new_l */ for(new_l = headents[new_e].hd_text; new_l->next; new_l=new_l->next) ; /* and cur_l (required in fix_and... */ ods.cur_l = new_l; } else{ ++ods.p_line; fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); InvertPrompt(ods.cur_e, TRUE); return(0); } fix_mangle_and_err(&mangled, &err, headents[ods.cur_e].name); } InvertPrompt(new_e, TRUE); sgarbk = 1; } ods.cur_e = new_e; /* update pointers */ ods.cur_l = new_l; if(ods.p_off > (len = strlen(ods.cur_l->text))) ods.p_off = len; if(status > 0 || fullpaint){ UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); } return(1); } /* * */ int AppendAttachment(fn, sz, cmt) char *fn, *sz, *cmt; { int a_e, status, spaces; struct hdr_line *lp; /*--- Find headerentry that is attachments (only first) --*/ for(a_e = 0; headents[a_e].name != NULL; a_e++ ) if(headents[a_e].is_attach){ /* make sure field stays displayed */ headents[a_e].rich_header = 0; headents[a_e].display_it = 1; break; } /* append new attachment line */ for(lp = headents[a_e].hd_text; lp->next; lp=lp->next) ; /* build new attachment line */ if(lp->text[0]){ /* adding a line? */ strcat(lp->text, ","); /* append delimiter */ if(lp->next = HALLOC()){ /* allocate new line */ lp->next->prev = lp; lp->next->next = NULL; lp = lp->next; } else{ emlwrite("\007Can't allocate line for new attachment!", NULL); return(0); } } spaces = (*fn == '\"') ? 0 : (strpbrk(fn, "(), \t") != NULL); sprintf(lp->text, "%s%s%s (%s) \"%.*s\"", spaces ? "\"" : "", fn, spaces ? "\"" : "", sz ? sz : "", 80, cmt ? cmt : ""); /* validate the new attachment, and reformat if needed */ if(status = SyncAttach()){ if(status < 0) emlwrite("\007Problem attaching: %s", fn); if(FormatLines(headents[a_e].hd_text, "", term.t_ncol - headents[a_e].prlen, headents[a_e].break_on_comma, 0) == -1){ emlwrite("\007Format lines failed!", NULL); return(0); } } UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, status != 0); PaintBody(1); return(status != 0); } /* * LineEdit - the idea is to manage 7 bit ascii character only input. * Always use insert mode and handle line wrapping * * returns: * Any characters typed in that aren't printable * (i.e. commands) * * notes: * Assume we are guaranteed that there is sufficiently * more buffer space in a line than screen width (just one * less thing to worry about). If you want to change this, * then pputc will have to be taught to check the line buffer * length, and HALLOC() will probably have to become a func. */ LineEdit(allowedit) int allowedit; { register struct hdr_line *lp; /* temporary line pointer */ register int i; register int ch = 0; register int status; /* various func's return val */ register char *tbufp; /* temporary buffer pointers */ int skipmove = 0; char *strng; int last_key; /* last keystroke */ strng = ods.cur_l->text; /* initialize offsets */ ods.p_len = strlen(strng); if(ods.p_off < 0) /* offset within range? */ ods.p_off = 0; else if(ods.p_off > ods.p_len) ods.p_off = ods.p_len; else if(ods.p_off > LINELEN()) /* shouldn't happen, but */ ods.p_off = LINELEN(); /* you never know... */ while(1){ /* edit the line... */ if(skipmove) skipmove = 0; else HeaderPaintCursor(); last_key = ch; (*term.t_flush)(); /* get everything out */ #ifdef MOUSE mouse_in_content(KEY_MOUSE, -1, -1, 0, 0); register_mfunc(mouse_in_content,2,0,term.t_nrow-(term.t_mrow+1), term.t_ncol); #endif #ifdef _WINDOWS mswin_setdndcallback (composer_file_drop); mswin_mousetrackcallback(pico_cursor); #endif ch = GetKey(); if (term.t_nrow < 6 && ch != NODATA){ (*term.t_beep)(); emlwrite("Please make the screen bigger.", NULL); continue; } #ifdef MOUSE clear_mfunc(mouse_in_content); #endif #ifdef _WINDOWS mswin_cleardndcallback (); mswin_mousetrackcallback(NULL); #endif switch(ch){ case DEL : if(gmode & P_DELRUBS) ch = KEY_DEL; default : (*Pmaster->keybinput)(); if(!time_to_check()) break; case NODATA : /* new mail ? */ if((*Pmaster->newmail)(ch == NODATA ? 0 : 2, 1) >= 0){ int rv; if(km_popped){ term.t_mrow = 2; curwp->w_ntrows -= 2; } clearcursor(); mlerase(); rv = (*Pmaster->showmsg)(ch); ttresize(); picosigs(); if(rv) /* Did showmsg corrupt the display? */ PaintBody(0); /* Yes, repaint */ mpresf = 1; if(km_popped){ term.t_mrow = 0; curwp->w_ntrows += 2; } } clearcursor(); movecursor(ods.p_line, ods.p_off+headents[ods.cur_e].prlen); if(ch == NODATA) /* GetKey timed out */ continue; break; } if(mpresf){ /* blast old messages */ if(mpresf++ > NMMESSDELAY){ /* every few keystrokes */ mlerase(); movecursor(ods.p_line, ods.p_off+headents[ods.cur_e].prlen); } } if(VALID_KEY(ch)){ /* char input */ /* * if we are allowing editing, insert the new char * end up leaving tbufp pointing to newly * inserted character in string, and offset to the * index of the character after the inserted ch ... */ if(allowedit){ if(headents[ods.cur_e].only_file_chars && !fallowc((unsigned char) ch)){ /* no garbage in filenames */ emlwrite("\007Can't have a '%c' in folder name", (void *) ch); continue; } else if(headents[ods.cur_e].is_attach && intag(strng,ods.p_off)){ emlwrite("\007Can't edit attachment number!", NULL); continue; } if(headents[ods.cur_e].single_space){ if(ch == ' ' && (strng[ods.p_off]==' ' || strng[ods.p_off-1]==' ')) continue; } /* * go ahead and add the character... */ tbufp = &strng[++ods.p_len]; /* find the end */ do{ *tbufp = tbufp[-1]; } while(--tbufp > &strng[ods.p_off]); /* shift right */ strng[ods.p_off++] = ch; /* add char to str */ /* mark this entry dirty */ headents[ods.cur_e].sticky = 1; headents[ods.cur_e].dirty = 1; /* * then find out where things fit... */ if(ods.p_len < LINELEN()){ CELL c; c.c = ch; c.a = 0; if(pinsert(c)){ /* add char to str */ skipmove++; /* must'a been optimal */ continue; /* on to the next! */ } } else{ if((status = FormatLines(ods.cur_l, "", LINELEN(), headents[ods.cur_e].break_on_comma,0)) == -1){ (*term.t_beep)(); continue; } else{ /* * during the format, the dot may have moved * down to the next line... */ if(ods.p_off >= strlen(strng)){ ods.p_line++; ods.p_off -= strlen(strng); ods.cur_l = ods.cur_l->next; strng = ods.cur_l->text; } ods.p_len = strlen(strng); } UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); PaintBody(1); continue; } } else{ rdonly(); continue; } } else { /* interpret ch as a command */ switch (ch = normalize_cmd(ch, ckm, 2)) { case (CTRL|'@') : /* word skip */ while(strng[ods.p_off] && isalnum((unsigned char)strng[ods.p_off])) ods.p_off++; /* skip any text we're in */ while(strng[ods.p_off] && !isalnum((unsigned char)strng[ods.p_off])) ods.p_off++; /* skip any whitespace after it */ if(strng[ods.p_off] == '\0'){ ods.p_off = 0; /* end of line, let caller handle it */ return(KEY_DOWN); } continue; case (CTRL|'K') : /* kill line cursor's on */ if(!allowedit){ rdonly(); continue; } lp = ods.cur_l; if (!(gmode & MDDTKILL)) ods.p_off = 0; if(KillHeaderLine(lp, (last_key == (CTRL|'K')))){ if(optimize && !(ods.cur_l->prev==NULL && ods.cur_l->next==NULL)) scrollup(wheadp, ods.p_line, 1); if(ods.cur_l->next == NULL) if(zotcomma(ods.cur_l->text)){ if(ods.p_off > 0) ods.p_off = strlen(ods.cur_l->text); } i = (ods.p_line == COMPOSER_TOP_LINE); UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, TRUE); if(km_popped){ km_popped--; movecursor(term.t_nrow, 0); peeol(); } PaintBody(1); } strng = ods.cur_l->text; ods.p_len = strlen(strng); headents[ods.cur_e].sticky = 0; headents[ods.cur_e].dirty = 1; continue; case (CTRL|'U') : /* un-delete deleted lines */ if(!allowedit){ rdonly(); continue; } if(SaveHeaderLines()){ UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); if(km_popped){ km_popped--; movecursor(term.t_nrow, 0); peeol(); } PaintBody(1); strng = ods.cur_l->text; ods.p_len = strlen(strng); headents[ods.cur_e].sticky = 1; headents[ods.cur_e].dirty = 1; } else emlwrite("Problem Unkilling text", NULL); continue; case (CTRL|'F') : case KEY_RIGHT: /* move character right */ if(ods.p_off < ods.p_len){ pputc(pscr(ods.p_line, (ods.p_off++)+headents[ods.cur_e].prlen)->c,0); skipmove++; continue; } else if(gmode & MDHDRONLY) continue; ods.p_off = 0; return(KEY_DOWN); case (CTRL|'B') : case KEY_LEFT : /* move character left */ if(ods.p_off > 0){ ods.p_off--; continue; } if(ods.p_line != COMPOSER_TOP_LINE) ods.p_off = 1000; /* put cursor at end of line */ return(KEY_UP); case (CTRL|'M') : /* goto next field */ ods.p_off = 0; return(KEY_DOWN); case KEY_HOME : case (CTRL|'A') : /* goto beginning of line */ ods.p_off = 0; continue; case KEY_END : case (CTRL|'E') : /* goto end of line */ ods.p_off = ods.p_len; continue; case (CTRL|'D') : /* blast this char */ case KEY_DEL : if(!allowedit){ rdonly(); continue; } else if(ods.p_off >= strlen(strng)) continue; if(headents[ods.cur_e].is_attach && intag(strng, ods.p_off)){ emlwrite("\007Can't edit attachment number!", NULL); continue; } pputc(strng[ods.p_off++], 0); /* drop through and rubout */ case DEL : /* blast previous char */ case (CTRL|'H') : if(!allowedit){ rdonly(); continue; } if(headents[ods.cur_e].is_attach && intag(strng, ods.p_off-1)){ emlwrite("\007Can't edit attachment number!", NULL); continue; } if(ods.p_off > 0){ /* just shift left one char */ ods.p_len--; headents[ods.cur_e].dirty = 1; if(ods.p_len == 0) headents[ods.cur_e].sticky = 0; else headents[ods.cur_e].sticky = 1; tbufp = &strng[--ods.p_off]; while(*tbufp++ != '\0') tbufp[-1] = *tbufp; tbufp = &strng[ods.p_off]; if(pdel()) /* physical screen delete */ skipmove++; /* must'a been optimal */ } else{ /* may have work to do */ if(ods.cur_l->prev == NULL){ (*term.t_beep)(); /* no erase into next field */ continue; } ods.p_line--; ods.cur_l = ods.cur_l->prev; strng = ods.cur_l->text; if((i=strlen(strng)) > 0){ strng[i-1] = '\0'; /* erase the character */ ods.p_off = i-1; } else{ headents[ods.cur_e].sticky = 0; ods.p_off = 0; } tbufp = &strng[ods.p_off]; } if((status = FormatLines(ods.cur_l, "", LINELEN(), headents[ods.cur_e].break_on_comma,0))==-1){ (*term.t_beep)(); continue; } else{ /* * beware, the dot may have moved... */ while((ods.p_len=strlen(strng)) < ods.p_off){ ods.p_line++; ods.p_off -= strlen(strng); ods.cur_l = ods.cur_l->next; strng = ods.cur_l->text; ods.p_len = strlen(strng); tbufp = &strng[ods.p_off]; status = TRUE; } UpdateHeader(0); PaintHeader(COMPOSER_TOP_LINE, FALSE); if(status == TRUE) PaintBody(1); } movecursor(ods.p_line, ods.p_off+headents[ods.cur_e].prlen); if(skipmove) continue; break; default : return(ch); } } while (*tbufp != '\0') /* synchronizing loop */ pputc(*tbufp++, 0); if(ods.p_len < LINELEN()) peeol(); } } void HeaderPaintCursor() { movecursor(ods.p_line, ods.p_off+headents[ods.cur_e].prlen); } /* * FormatLines - Place the given text at the front of the given line->text * making sure to properly format the line, then check * all lines below for proper format. * * notes: * Not much optimization at all. Right now, it recursively * fixes all remaining lines in the entry. Some speed might * gained if this was built to iteratively scan the lines. * * returns: * -1 on error * FALSE if only this line is changed * TRUE if text below the first line is changed */ FormatLines(h, instr, maxlen, break_on_comma, quoted) struct hdr_line *h; /* where to begin formatting */ char *instr; /* input string */ int maxlen; /* max chars on a line */ int break_on_comma; /* break lines on commas */ int quoted; /* this line inside quotes */ { int retval = FALSE; register int i, l; char *ostr; /* pointer to output string */ register char *breakp; /* pointer to line break */ register char *bp, *tp; /* temporary pointers */ char *buf; /* string to add later */ struct hdr_line *nlp, *lp; ostr = h->text; nlp = h->next; l = strlen(instr) + strlen(ostr); if((buf = (char *)malloc(l+10)) == NULL) return(-1); if(l >= maxlen){ /* break then fixup below */ if((l=strlen(instr)) < maxlen){ /* room for more */ if(break_on_comma && (bp = (char *)strqchr(instr, ',', "ed, -1))){ bp += (bp[1] == ' ') ? 2 : 1; for(tp = bp; *tp && *tp == ' '; tp++) ; strcpy(buf, tp); strcat(buf, ostr); for(i = 0; &instr[i] < bp; i++) ostr[i] = instr[i]; ostr[i] = '\0'; retval = TRUE; } else{ breakp = break_point(ostr, maxlen-strlen(instr), break_on_comma ? ',' : ' ', break_on_comma ? "ed : NULL); if(breakp == ostr){ /* no good breakpoint */ if(break_on_comma && *breakp == ','){ breakp = ostr + 1; retval = TRUE; } else if(strchr(instr,(break_on_comma && !quoted)?',':' ')){ strcpy(buf, ostr); strcpy(ostr, instr); } else{ /* instr's broken as we can get it */ breakp = &ostr[maxlen-strlen(instr)-1]; retval = TRUE; } } else retval = TRUE; if(retval){ strcpy(buf, breakp); /* save broken line */ if(breakp == ostr){ strcpy(ostr, instr); /* simple if no break */ } else{ *breakp = '\0'; /* more work to break it */ i = strlen(instr); /* * shift ostr i chars */ for(bp=breakp; bp >= ostr && i; bp--) *(bp+i) = *bp; for(tp=ostr, bp=instr; *bp != '\0'; tp++, bp++) *tp = *bp; /* then add instr */ } } } } /* * Short-circuit the recursion in this case. * No time right now to figure out how to do it better. */ else if(l > 2*maxlen){ char *instrp, *saveostr = NULL; retval = TRUE; if(ostr && *ostr){ saveostr = (char *)malloc(strlen(ostr) + 1); strcpy(saveostr, ostr); ostr[0] = '\0'; } instrp = instr; while(l > 2*maxlen){ if(break_on_comma || maxlen == 1){ breakp = (!(bp = strqchr(instrp, ',', "ed, maxlen)) || bp - instrp >= maxlen || maxlen == 1) ? &instrp[maxlen] : bp + ((bp[1] == ' ') ? 2 : 1); } else{ breakp = break_point(instrp, maxlen, ' ', NULL); if(breakp == instrp) /* no good break point */ breakp = &instrp[maxlen - 1]; } for(tp=ostr,bp=instrp; bp < breakp; tp++, bp++) *tp = *bp; *tp = '\0'; l -= (breakp - instrp); instrp = breakp; if((lp = HALLOC()) == NULL){ emlwrite("Can't allocate any more lines for header!", NULL); free(buf); return(-1); } lp->next = h->next; if(h->next) h->next->prev = lp; h->next = lp; lp->prev = h; lp->text[0] = '\0'; h = h->next; ostr = h->text; } /* * Now l is still > maxlen. Do it the recursive way, * like the else clause below. Surely we could fix up the * flow control some here, but this works for now. */ nlp = h->next; instr = instrp; if(saveostr){ strcpy(ostr, saveostr); free(saveostr); } if(break_on_comma || maxlen == 1){ breakp = (!(bp = strqchr(instrp, ',', "ed, maxlen)) || bp - instrp >= maxlen || maxlen == 1) ? &instrp[maxlen] : bp + ((bp[1] == ' ') ? 2 : 1); } else{ breakp = break_point(instrp, maxlen, ' ', NULL); if(breakp == instrp) /* no good break point */ breakp = &instrp[maxlen - 1]; } strcpy(buf, breakp); /* save broken line */ strcat(buf, ostr); /* add line that was there */ for(tp=ostr,bp=instr; bp < breakp; tp++, bp++) *tp = *bp; *tp = '\0'; } else{ /* instr > maxlen ! */ if(break_on_comma || maxlen == 1){ breakp = (!(bp = strqchr(instr, ',', "ed, maxlen)) || bp - instr >= maxlen || maxlen == 1) ? &instr[maxlen] : bp + ((bp[1] == ' ') ? 2 : 1); } else{ breakp = break_point(instr, maxlen, ' ', NULL); if(breakp == instr) /* no good break point */ breakp = &instr[maxlen - 1]; } strcpy(buf, breakp); /* save broken line */ strcat(buf, ostr); /* add line that was there */ for(tp=ostr,bp=instr; bp < breakp; tp++, bp++) *tp = *bp; *tp = '\0'; } if(nlp == NULL){ /* no place to add below? */ if((lp = HALLOC()) == NULL){ emlwrite("Can't allocate any more lines for header!", NULL); free(buf); return(-1); } if(optimize && (i = physical_line(h)) != -1) scrolldown(wheadp, i - 1, 1); h->next = lp; /* fix up links */ lp->prev = h; lp->next = NULL; lp->text[0] = '\0'; nlp = lp; retval = TRUE; } } else{ /* combined length < max */ if(*instr){ strcpy(buf, instr); /* insert instr before ostr */ strcat(buf, ostr); strcpy(ostr, buf); } *buf = '\0'; breakp = NULL; if(break_on_comma && (breakp = strqchr(ostr, ',', "ed, -1))){ breakp += (breakp[1] == ' ') ? 2 : 1; strcpy(buf, breakp); *breakp = '\0'; if(strlen(buf) && !nlp){ if((lp = HALLOC()) == NULL){ emlwrite("Can't allocate any more lines for header!",NULL); free(buf); return(-1); } if(optimize && (i = physical_line(h)) != -1) scrolldown(wheadp, i - 1, 1); h->next = lp; /* fix up links */ lp->prev = h; lp->next = NULL; lp->text[0] = '\0'; nlp = lp; retval = TRUE; } } if(nlp){ if(!strlen(buf) && !breakp){ if(strlen(ostr) + strlen(nlp->text) >= maxlen){ breakp = break_point(nlp->text, maxlen-strlen(ostr), break_on_comma ? ',' : ' ', break_on_comma ? "ed : NULL); if(breakp == nlp->text){ /* commas this line? */ for(tp=ostr; *tp && *tp != ' '; tp++) ; if(!*tp){ /* no commas, get next best */ breakp += maxlen - strlen(ostr) - 1; retval = TRUE; } else retval = FALSE; } else retval = TRUE; if(retval){ /* only if something to do */ for(tp = &ostr[strlen(ostr)],bp=nlp->text; bptext, bp=breakp; *bp != '\0'; tp++, bp++) *tp = *bp; /* shift next line to left */ *tp = '\0'; } } else{ strcat(ostr, nlp->text); if(optimize && (i = physical_line(nlp)) != -1) scrollup(wheadp, i, 1); hldelete(nlp); if(!(nlp = h->next)){ free(buf); return(TRUE); /* can't go further */ } else retval = TRUE; /* more work to do? */ } } } else{ free(buf); return(FALSE); } } i = FormatLines(nlp, buf, maxlen, break_on_comma, quoted); free(buf); switch(i){ case -1: /* bubble up worst case */ return(-1); case FALSE: if(retval == FALSE) return(FALSE); default: return(TRUE); } } /* * PaintHeader - do the work of displaying the header from the given * physical screen line the end of the header. * * 17 July 91 - fixed reshow to deal with arbitrarily large headers. */ void PaintHeader(line, clear) int line; /* physical line on screen */ int clear; /* clear before painting */ { register struct hdr_line *lp; register char *bufp; register int curline; register int curoffset; char buf[NLINE]; int e; if(clear) pclear(COMPOSER_TOP_LINE, ComposerTopLine); curline = COMPOSER_TOP_LINE; curoffset = 0; for(lp = ods.top_l, e = ods.top_e; ; curline++){ if((curline == line) || ((lp = next_hline(&e, lp)) == NULL)) break; } while(headents[e].name != NULL){ /* begin to redraw */ while(lp != NULL){ buf[0] = '\0'; if((!lp->prev || curline == COMPOSER_TOP_LINE) && !curoffset){ if(InvertPrompt(e, (e == ods.cur_e && ComposerEditing)) == -1 && !is_blank(curline, 0, headents[e].prlen)) sprintf(buf, "%*s", headents[e].prlen, " "); } else if(!is_blank(curline, 0, headents[e].prlen)) sprintf(buf, "%*s", headents[e].prlen, " "); if(*(bufp = buf) != '\0'){ /* need to paint? */ movecursor(curline, 0); /* paint the line... */ while(*bufp != '\0') pputc(*bufp++, 0); } bufp = &(lp->text[curoffset]); /* skip chars already there */ curoffset += headents[e].prlen; while(pscr(curline, curoffset) != NULL && *bufp == pscr(curline, curoffset)->c && *bufp != '\0'){ bufp++; if(++curoffset >= term.t_ncol) break; } if(*bufp != '\0'){ /* need to move? */ movecursor(curline, curoffset); while(*bufp != '\0'){ /* display what's not there */ pputc(*bufp++, 0); curoffset++; } } if(curoffset < term.t_ncol && !is_blank(curline, curoffset, term.t_ncol - curoffset)){ movecursor(curline, curoffset); peeol(); } curline++; curoffset = 0; if(curline >= BOTTOM()) break; lp = lp->next; } if(curline >= BOTTOM()) return; /* don't paint delimiter */ while(headents[++e].name != NULL) if(headents[e].display_it){ lp = headents[e].hd_text; break; } } display_delimiter(ComposerEditing ? 0 : 1); } /* * PaintBody() - generic call to handle repainting everything BUT the * header * * notes: * The header redrawing in a level 0 body paint gets done * in update() */ void PaintBody(level) int level; { curwp->w_flag |= WFHARD; /* make sure framing's right */ if(level == 0) /* specify what to update */ sgarbf = TRUE; update(); /* display message body */ if(level == 0 && ComposerEditing){ mlerase(); /* clear the error line */ ShowPrompt(); } } /* * display_for_send - paint the composer from the top line and return. */ void display_for_send() { int i = 0; struct hdr_line *l; /* if first header line isn't displayed, there's work to do */ if(headents && ((l = first_hline(&i)) != ods.top_l || ComposerTopLine == COMPOSER_TOP_LINE || !ods.p_line)){ struct on_display orig_ods; int orig_edit = ComposerEditing, orig_ct_line = ComposerTopLine; /* * fake that the cursor's in the first header line * and force repaint... */ orig_ods = ods; ods.cur_e = i; ods.top_l = ods.cur_l = l; ods.top_e = ods.cur_e; ods.p_line = COMPOSER_TOP_LINE; ComposerEditing = TRUE; /* to fool update() */ setimark(FALSE, 1); /* remember where we were */ gotobob(FALSE, 1); UpdateHeader(0); /* redraw whole enchilada */ PaintHeader(COMPOSER_TOP_LINE, TRUE); PaintBody(0); ods = orig_ods; /* restore original state */ ComposerEditing = orig_edit; ComposerTopLine = curwp->w_toprow = orig_ct_line; curwp->w_ntrows = BOTTOM() - ComposerTopLine; swapimark(FALSE, 1); /* in case we don't exit, set up restoring the screen */ sgarbf = TRUE; /* force redraw */ } } /* * ArrangeHeader - set up display parm such that header is reasonably * displayed */ void ArrangeHeader() { int e; register struct hdr_line *l; ods.p_line = ods.p_off = 0; e = ods.top_e = 0; l = ods.top_l = headents[e].hd_text; while(headents[e+1].name || (l && l->next)) if(l = next_sel_hline(&e, l)){ ods.cur_l = l; ods.cur_e = e; } UpdateHeader(1); } /* * ComposerHelp() - display mail help in a context sensitive way * based on the level passed ... */ ComposerHelp(level) int level; { char buf[80]; VARS_TO_SAVE *saved_state; curwp->w_flag |= WFMODE; sgarbf = TRUE; if(level < 0 || !headents[level].name){ (*term.t_beep)(); emlwrite("Sorry, I can't help you with that.", NULL); sleep(2); return(FALSE); } sprintf(buf, "Help for %s %.40s Field", (Pmaster->pine_flags & MDHDRONLY) ? "Address Book" : "Composer", headents[level].name); saved_state = save_pico_state(); (*Pmaster->helper)(headents[level].help, buf, 1); if(saved_state){ restore_pico_state(saved_state); free_pico_state(saved_state); } ttresize(); picosigs(); /* restore altered handlers */ return(TRUE); } /* * ToggleHeader() - set or unset pico values to the full screen size * painting header if need be. */ ToggleHeader(show) int show; { /* * check to see if we need to display the header... */ if(show){ UpdateHeader(0); /* figure bounds */ PaintHeader(COMPOSER_TOP_LINE, FALSE); /* draw it */ } else{ /* * set bounds for no header display */ curwp->w_toprow = ComposerTopLine = COMPOSER_TOP_LINE; curwp->w_ntrows = BOTTOM() - ComposerTopLine; } return(TRUE); } /* * HeaderLen() - return the length in lines of the exposed portion of the * header */ HeaderLen() { register struct hdr_line *lp; int e; int i; i = 1; lp = ods.top_l; e = ods.top_e; while(lp != NULL){ lp = next_hline(&e, lp); i++; } return(i); } /* * first_hline() - return a pointer to the first displayable header line * * returns: * 1) pointer to first displayable line in header and header * entry, via side effect, that the first line is a part of * 2) NULL if no next line, leaving entry at LASTHDR */ struct hdr_line * first_hline(entry) int *entry; { /* init *entry so we're sure to start from the top */ for(*entry = 0; headents[*entry].name; (*entry)++) if(headents[*entry].display_it) return(headents[*entry].hd_text); *entry = 0; return(NULL); /* this shouldn't happen */ } /* * first_sel_hline() - return a pointer to the first selectable header line * * returns: * 1) pointer to first selectable line in header and header * entry, via side effect, that the first line is a part of * 2) NULL if no next line, leaving entry at LASTHDR */ struct hdr_line * first_sel_hline(entry) int *entry; { /* init *entry so we're sure to start from the top */ for(*entry = 0; headents[*entry].name; (*entry)++) if(headents[*entry].display_it && !headents[*entry].blank) return(headents[*entry].hd_text); *entry = 0; return(NULL); /* this shouldn't happen */ } /* * next_hline() - return a pointer to the next line structure * * returns: * 1) pointer to next displayable line in header and header * entry, via side effect, that the next line is a part of * 2) NULL if no next line, leaving entry at LASTHDR */ struct hdr_line * next_hline(entry, line) int *entry; struct hdr_line *line; { if(line == NULL) return(NULL); if(line->next == NULL){ while(headents[++(*entry)].name != NULL){ if(headents[*entry].display_it) return(headents[*entry].hd_text); } --(*entry); return(NULL); } else return(line->next); } /* * next_sel_hline() - return a pointer to the next selectable line structure * * returns: * 1) pointer to next selectable line in header and header * entry, via side effect, that the next line is a part of * 2) NULL if no next line, leaving entry at LASTHDR */ struct hdr_line * next_sel_hline(entry, line) int *entry; struct hdr_line *line; { if(line == NULL) return(NULL); if(line->next == NULL){ while(headents[++(*entry)].name != NULL){ if(headents[*entry].display_it && !headents[*entry].blank) return(headents[*entry].hd_text); } --(*entry); return(NULL); } else return(line->next); } /* * prev_hline() - return a pointer to the next line structure back * * returns: * 1) pointer to previous displayable line in header and * the header entry that the next line is a part of * via side effect * 2) NULL if no next line, leaving entry unchanged from * the value it had on entry. */ struct hdr_line * prev_hline(entry, line) int *entry; struct hdr_line *line; { if(line == NULL) return(NULL); if(line->prev == NULL){ int orig_entry; orig_entry = *entry; while(--(*entry) >= 0){ if(headents[*entry].display_it){ line = headents[*entry].hd_text; while(line->next != NULL) line = line->next; return(line); } } *entry = orig_entry; return(NULL); } else return(line->prev); } /* * prev_sel_hline() - return a pointer to the previous selectable line * * returns: * 1) pointer to previous selectable line in header and * the header entry that the next line is a part of * via side effect * 2) NULL if no next line, leaving entry unchanged from * the value it had on entry. */ struct hdr_line * prev_sel_hline(entry, line) int *entry; struct hdr_line *line; { if(line == NULL) return(NULL); if(line->prev == NULL){ int orig_entry; orig_entry = *entry; while(--(*entry) >= 0){ if(headents[*entry].display_it && !headents[*entry].blank){ line = headents[*entry].hd_text; while(line->next != NULL) line = line->next; return(line); } } *entry = orig_entry; return(NULL); } else return(line->prev); } /* * first_requested_hline() - return pointer to first line that pico's caller * asked that we start on. */ struct hdr_line * first_requested_hline(ent) int *ent; { int i, reqfield; struct hdr_line *rv = NULL; for(reqfield = -1, i = 0; headents[i].name; i++) if(headents[i].start_here){ headents[i].start_here = 0; /* clear old setting */ if(reqfield < 0){ /* if not already, set up */ headents[i].display_it = 1; /* make sure it's shown */ *ent = reqfield = i; rv = headents[i].hd_text; } } return(rv); } /* * UpdateHeader() - determines the best range of lines to be displayed * using the global ods value for the current line and the * top line, also sets ComposerTopLine and pico limits * * showtop -- Attempt to show all header lines if they'll fit. * * notes: * This is pretty ugly because it has to keep the current line * on the screen in a reasonable location no matter what. * There are also a couple of rules to follow: * 1) follow paging conventions of pico (ie, half page * scroll) * 2) if more than one page, always display last half when * pline is toward the end of the header * * returns: * TRUE if anything changed (side effects: new p_line, top_l * top_e, and pico parms) * FALSE if nothing changed * */ UpdateHeader(showtop) int showtop; { register struct hdr_line *lp; int i, le; int ret = FALSE; int old_top = ComposerTopLine; int old_p = ods.p_line; if(ods.p_line < COMPOSER_TOP_LINE || ((ods.p_line == ComposerTopLine-2) ? 2: 0) + ods.p_line >= BOTTOM()){ /* NewTop if cur header line is at bottom of screen or two from */ /* the bottom of the screen if cur line is bottom header line */ NewTop(showtop); /* get new top_l */ ret = TRUE; } else{ /* make sure p_line's OK */ i = COMPOSER_TOP_LINE; lp = ods.top_l; le = ods.top_e; while(lp != ods.cur_l){ /* * this checks to make sure cur_l is below top_l and that * cur_l is on the screen... */ if((lp = next_hline(&le, lp)) == NULL || ++i >= BOTTOM()){ NewTop(0); ret = TRUE; break; } } } ods.p_line = COMPOSER_TOP_LINE; /* find p_line... */ lp = ods.top_l; le = ods.top_e; while(lp && lp != ods.cur_l){ lp = next_hline(&le, lp); ods.p_line++; } if(!ret) ret = !(ods.p_line == old_p); ComposerTopLine = ods.p_line; /* figure top composer line */ while(lp && ComposerTopLine <= BOTTOM()){ lp = next_hline(&le, lp); ComposerTopLine += (lp) ? 1 : 2; /* allow for delim at end */ } if(!ret) ret = !(ComposerTopLine == old_top); if(wheadp->w_toprow != ComposerTopLine){ /* update pico params... */ wheadp->w_toprow = ComposerTopLine; wheadp->w_ntrows = ((i = BOTTOM() - ComposerTopLine) > 0) ? i : 0; ret = TRUE; } return(ret); } /* * NewTop() - calculate a new top_l based on the cur_l * * showtop -- Attempt to show all the header lines if they'll fit * * returns: * with ods.top_l and top_e pointing at a reasonable line * entry */ void NewTop(showtop) int showtop; { register struct hdr_line *lp; register int i; int e; lp = ods.cur_l; e = ods.cur_e; i = showtop ? FULL_SCR() : HALF_SCR(); while(lp != NULL && --i){ ods.top_l = lp; ods.top_e = e; lp = prev_hline(&e, lp); } } /* * display_delimiter() - just paint the header/message body delimiter with * inverse value specified by state. */ void display_delimiter(state) int state; { register char *bufp; if(ComposerTopLine - 1 >= BOTTOM()) /* silently forget it */ return; bufp = (gmode & MDHDRONLY) ? "" : HDR_DELIM; if(state == delim_ps){ /* optimize ? */ for(delim_ps = 0; bufp[delim_ps] && pscr(ComposerTopLine-1,delim_ps) != NULL && pscr(ComposerTopLine-1,delim_ps)->c == bufp[delim_ps];delim_ps++) ; if(bufp[delim_ps] == '\0' && !(gmode & MDHDRONLY)){ delim_ps = state; return; /* already displayed! */ } } delim_ps = state; movecursor(ComposerTopLine - 1, 0); if(state) (*term.t_rev)(1); while(*bufp != '\0') pputc(*bufp++, state ? 1 : 0); if(state) (*term.t_rev)(0); peeol(); } /* * InvertPrompt() - invert the prompt associated with header entry to state * state (true if invert, false otherwise). * returns: * non-zero if nothing done * 0 if prompt inverted successfully * * notes: * come to think of it, this func and the one above could * easily be combined */ InvertPrompt(entry, state) int entry, state; { register char *bufp; register int i; bufp = headents[entry].prompt; /* fresh prompt paint */ if((i = entry_line(entry, FALSE)) == -1) return(-1); /* silently forget it */ if(entry < 16 && (invert_ps&(1<c == bufp[j]; j++) ; if(bufp[j] == '\0'){ if(state) invert_ps |= 1< 16, cannot be stored in invert_ps */ if(state) invert_ps |= 1<rich_header && h->name != NULL; h++) ; is_on = h->display_it; for(h = headents; h->name != NULL; h++) if(h->rich_header) h->display_it = ! is_on; return(is_on); } /* * entry_line() - return the physical line on the screen associated * with the given header entry field. Note: the field * may span lines, so if the last char is set, return * the appropriate value. * * returns: * 1) physical line number of entry * 2) -1 if entry currently not on display */ entry_line(entry, lastchar) int entry, lastchar; { register int p_line = COMPOSER_TOP_LINE; int i; register struct hdr_line *line; for(line = ods.top_l, i = ods.top_e; headents && headents[i].name && i <= entry; p_line++){ if(p_line >= BOTTOM()) break; if(i == entry){ if(lastchar){ if(line->next == NULL) return(p_line); } else if(line->prev == NULL) return(p_line); else return(-1); } line = next_hline(&i, line); } return(-1); } /* * physical_line() - return the physical line on the screen associated * with the given header line pointer. * * returns: * 1) physical line number of entry * 2) -1 if entry currently not on display */ physical_line(l) struct hdr_line *l; { register int p_line = COMPOSER_TOP_LINE; register struct hdr_line *lp; int i; for(lp=ods.top_l, i=ods.top_e; headents[i].name && lp != NULL; p_line++){ if(p_line >= BOTTOM()) break; if(lp == l) return(p_line); lp = next_hline(&i, lp); } return(-1); } /* * call_builder() - resolve any nicknames in the address book associated * with the given entry... * * NOTES: * * BEWARE: this function can cause cur_l and top_l to get lost so BE * CAREFUL before and after you call this function!!! * * There could to be something here to resolve cur_l and top_l * reasonably into the new linked list for this entry. * * The reason this would mostly work without it is resolve_niks gets * called for the most part in between fields. Since we're moving * to the beginning or end (i.e. the next/prev pointer in the old * freed cur_l is NULL) of the next entry, we get a new cur_l * pointing at a good line. Then since top_l is based on cur_l in * NewTop() we have pretty much lucked out. * * Where we could get burned is in a canceled exit (ctrl|x). Here * nicknames get resolved into addresses, which invalidates cur_l * and top_l. Since we don't actually leave, we could begin editing * again with bad pointers. This would usually results in a nice * core dump. * * NOTE: The mangled argument is a little strange. It's used on both * input and output. On input, if it is not set, then that tells the * builder not to do anything that might take a long time, like a * white pages lookup. On return, it tells the caller that the screen * and signals may have been mangled so signals should be reset, window * resized, and screen redrawn. * * RETURNS: * > 0 if any names where resolved, otherwise * 0 if not, or * < 0 on error * -1: move to next line * -2: don't move off this line */ call_builder(entry, mangled, err) struct headerentry *entry; int *mangled; char **err; { register int retval = 0; register int i; register struct hdr_line *line; int quoted = 0; char *sbuf; char *s = NULL; struct headerentry *e; BUILDER_ARG *nextarg, *arg = NULL, *headarg = NULL; VARS_TO_SAVE *saved_state; if(!entry->builder) return(0); line = entry->hd_text; i = 0; while(line != NULL){ i += term.t_ncol; line = line->next; } if((sbuf=(char *)malloc((unsigned) i)) == NULL){ emlwrite("Can't malloc space to expand address", NULL); return(-1); } *sbuf = '\0'; /* * cat the whole entry into one string... */ line = entry->hd_text; while(line != NULL){ i = strlen(line->text); /* * To keep pine address builder happy, addresses should be separated * by ", ". Add this space if needed, otherwise... * (This is some ancient requirement that is no longer needed.) * * If this line is NOT a continuation of the previous line, add * white space for pine's address builder if its not already there... * (This is some ancient requirement that is no longer needed.) * * Also if it's not a continuation (i.e., there's already and addr on * the line), and there's another line below, treat the new line as * an implied comma. * (This should only be done for address-type lines, not for regular * text lines like subjects. Key off of the break_on_comma bit which * should only be set on those that won't mind a comma being added.) */ if(entry->break_on_comma){ if(i && line->text[i-1] == ',') strcat(line->text, " "); /* help address builder */ else if(line->next != NULL && !strend(line->text, ',')){ if(strqchr(line->text, ',', "ed, -1)) strcat(line->text, ", "); /* implied comma */ } else if(line->prev != NULL && line->next != NULL){ if(strchr(line->prev->text, ' ') != NULL && line->text[i-1] != ' ') strcat(line->text, " "); } } strcat(sbuf, line->text); line = line->next; } if(entry->affected_entry){ /* check if any non-sticky affected entries */ for(e = entry->affected_entry; e; e = e->next_affected) if(!e->sticky) break; /* there is at least one non-sticky so make a list to pass */ if(e){ for(e = entry->affected_entry; e; e = e->next_affected){ if(!arg){ headarg = arg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG)); if(!arg){ emlwrite("Can't malloc space for fcc", NULL); return(-1); } else{ arg->next = NULL; arg->tptr = NULL; arg->aff = &(e->bldr_private); arg->me = &(entry->bldr_private); } } else{ nextarg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG)); if(!nextarg){ emlwrite("Can't malloc space for fcc", NULL); return(-1); } else{ nextarg->next = NULL; nextarg->tptr = NULL; nextarg->aff = &(e->bldr_private); nextarg->me = &(entry->bldr_private); arg->next = nextarg; arg = arg->next; } } if(!e->sticky){ line = e->hd_text; if(!(arg->tptr=(char *)malloc(strlen(line->text) + 1))){ emlwrite("Can't malloc space for fcc", NULL); return(-1); } else strcpy(arg->tptr, line->text); } } } } /* * Even if there are no affected entries, we still need the arg * to pass the "me" pointer. */ if(!headarg){ headarg = (BUILDER_ARG *)malloc(sizeof(BUILDER_ARG)); if(!headarg){ emlwrite("Can't malloc space", NULL); return(-1); } else{ headarg->next = NULL; headarg->tptr = NULL; headarg->aff = NULL; headarg->me = &(entry->bldr_private); } } /* * The builder may make a new call back to pico() so we save and * restore the pico state. */ saved_state = save_pico_state(); retval = (*entry->builder)(sbuf, &s, err, headarg, mangled); if(saved_state){ restore_pico_state(saved_state); free_pico_state(saved_state); } if(mangled && *mangled & BUILDER_MESSAGE_DISPLAYED){ *mangled &= ~ BUILDER_MESSAGE_DISPLAYED; if(mpresf == FALSE) mpresf = TRUE; } if(retval >= 0){ if(strcmp(sbuf, s)){ line = entry->hd_text; InitEntryText(s, entry); /* arrange new one */ zotentry(line); /* blast old list o'entries */ entry->dirty = 1; /* mark it dirty */ retval = 1; } for(e = entry->affected_entry, arg = headarg; e; e = e->next_affected, arg = arg ? arg->next : NULL){ if(!e->sticky){ line = e->hd_text; if(strcmp(line->text, arg ? arg->tptr : "")){ /* it changed */ /* make sure they see it if changed */ e->display_it = 1; InitEntryText(arg ? arg->tptr : "", e); if(line == ods.top_l) ods.top_l = e->hd_text; zotentry(line); /* blast old list o'entries */ e->dirty = 1; /* mark it dirty */ retval = 1; } } } } if(s) free(s); for(arg = headarg; arg; arg = nextarg){ /* Don't free xtra or me, they just point to headerentry data */ nextarg = arg->next; if(arg->tptr) free(arg->tptr); free(arg); } free(sbuf); return(retval); } void call_expander() { char **s = NULL; VARS_TO_SAVE *saved_state; int expret; if(!Pmaster->expander) return; /* * Since expander may make a call back to pico() we need to * save and restore pico state. */ if((saved_state = save_pico_state()) != NULL){ expret = (*Pmaster->expander)(headents, &s); restore_pico_state(saved_state); free_pico_state(saved_state); ttresize(); picosigs(); if(expret > 0 && s){ char *tbuf; int i, biggest = 100; struct headerentry *e; /* * Use tbuf to cat together multiple line entries before comparing. */ tbuf = (char *)malloc(biggest + 1); for(e = headents, i=0; e->name != NULL; e++,i++){ int sz = 0; struct hdr_line *line; while(e->name && e->blank) e++; if(e->name == NULL) continue; for(line = e->hd_text; line != NULL; line = line->next) sz += strlen(line->text); if(sz > biggest){ biggest = sz; free(tbuf); tbuf = (char *)malloc(biggest + 1); } tbuf[0] = '\0'; for(line = e->hd_text; line != NULL; line = line->next) strcat(tbuf, line->text); if(strcmp(tbuf, s[i])){ /* it changed */ struct hdr_line *zline; line = zline = e->hd_text; InitEntryText(s[i], e); /* * If any of the lines for this entry are current or * top, fix that. */ for(; line != NULL; line = line->next){ if(line == ods.top_l) ods.top_l = e->hd_text; if(line == ods.cur_l) ods.cur_l = e->hd_text; } zotentry(zline); /* blast old list o'entries */ } } free(tbuf); } if(s){ char **p; for(p = s; *p; p++) free(*p); free(s); } } return; } /* * strend - neglecting white space, returns TRUE if c is at the * end of the given line. otherwise FALSE. */ strend(s, ch) char *s; int ch; { register char *b; register char c; c = (char)ch; if(s == NULL) return(FALSE); if(*s == '\0') return(FALSE); b = &s[strlen(s)]; while(isspace((unsigned char)(*--b))){ if(b == s) return(FALSE); } return(*b == c); } /* * strqchr - returns pointer to first non-quote-enclosed occurance of ch in * the given string. otherwise NULL. * s -- the string * ch -- the character we're looking for * q -- q tells us if we start out inside quotes on entry and is set * correctly on exit. * m -- max characters we'll check for ch (set to -1 for no check) */ char * strqchr(s, ch, q, m) char *s; int ch; int *q; int m; { int quoted = (q) ? *q : 0; for(; s && *s && m != 0; s++, m--){ if(*s == '"'){ quoted = !quoted; if(q) *q = quoted; } if(!quoted && *s == ch) return(s); } return(NULL); } /* * KillHeaderLine() - kill a line in the header * * notes: * This is pretty simple. Just using the emacs kill buffer * and its accompanying functions to cut the text from lines. * * returns: * TRUE if hldelete worked * FALSE otherwise */ KillHeaderLine(l, append) struct hdr_line *l; int append; { register char *c; int i = ods.p_off; int nl = TRUE; if(!append) kdelete(); c = l->text; if (gmode & MDDTKILL){ if (c[i] == '\0') /* don't insert a new line after this line*/ nl = FALSE; /*put to be deleted part into kill buffer */ for (i=ods.p_off; c[i] != '\0'; i++) kinsert(c[i]); }else{ while(*c != '\0') /* splat out the line */ kinsert(*c++); } if (nl) kinsert('\n'); /* helpful to yank in body */ #ifdef _WINDOWS mswin_killbuftoclip (kremove); #endif if (gmode & MDDTKILL){ if (l->text[0]=='\0'){ if(l->next && l->prev) ods.cur_l = next_hline(&ods.cur_e, l); else if(l->prev) ods.cur_l = prev_hline(&ods.cur_e, l); if(l == ods.top_l) ods.top_l = ods.cur_l; return(hldelete(l)); } else { l->text[ods.p_off]='\0'; /* delete part of the line from the cursor */ return(TRUE); } }else{ if(l->next && l->prev) ods.cur_l = next_hline(&ods.cur_e, l); else if(l->prev) ods.cur_l = prev_hline(&ods.cur_e, l); if(l == ods.top_l) ods.top_l = ods.cur_l; return(hldelete(l)); /* blast it */ } } /* * SaveHeaderLines() - insert the saved lines in the list before the * current line in the header * * notes: * Once again, just using emacs' kill buffer and its * functions. * * returns: * TRUE if something good happend * FALSE otherwise */ SaveHeaderLines() { char *buf; /* malloc'd copy of buffer */ register char *bp; /* pointer to above buffer */ register unsigned i; /* index */ char *work_buf, *work_buf_begin; char empty[1]; int len, buf_len, work_buf_len, tentative_p_off = 0; struct hdr_line *travel, *tentative_cur_l = NULL; if(ksize()){ if((bp = buf = (char *)malloc(ksize()+5)) == NULL){ emlwrite("Can't malloc space for saved text", NULL); return(FALSE); } } else return(FALSE); for(i=0; i < ksize(); i++) if(kremove(i) != '\n') /* filter out newlines */ *bp++ = kremove(i); *bp = '\0'; while(--bp >= buf) /* kill trailing white space */ if(*bp != ' '){ if(ods.cur_l->text[0] != '\0'){ if(*bp == '>'){ /* inserting an address */ *++bp = ','; /* so add separator */ *++bp = '\0'; } } else{ /* nothing in field yet */ if(*bp == ','){ /* so blast any extra */ *bp = '\0'; /* separators */ } } break; } if (gmode & MDDTKILL){ /* insert new text at the dot position */ buf_len = strlen(buf); tentative_p_off = ods.p_off + buf_len; work_buf_len = strlen(ods.cur_l->text) + buf_len; work_buf = (char *) malloc((work_buf_len + 1) * sizeof(char)); if (work_buf == NULL) { emlwrite("Can't malloc space for saved text", NULL); return(FALSE); } sprintf(work_buf_begin = work_buf, "%.*s%s%s", ods.p_off, ods.cur_l->text, buf, &ods.cur_l->text[ods.p_off]); empty[0]='\0'; ods.p_off = 0; /* insert text in 256-byte chuks */ while(work_buf_len + ods.p_off > 256) { strncpy(&ods.cur_l->text[ods.p_off], work_buf, 256-ods.p_off); work_buf += (256 - ods.p_off); work_buf_len -= (256 - ods.p_off); if(FormatLines(ods.cur_l, empty, LINELEN(), headents[ods.cur_e].break_on_comma, 0) == -1) { i = FALSE; break; } else { i = TRUE; len = 0; travel = ods.cur_l; while (len < 256){ len += strlen(travel->text); if (len >= 256) break; /* * This comes after the break above because it will * be accounted for in the while loop below. */ if(!tentative_cur_l){ if(tentative_p_off <= strlen(travel->text)) tentative_cur_l = travel; else tentative_p_off -= strlen(travel->text); } travel = travel->next; } ods.cur_l = travel; ods.p_off = strlen(travel->text) - len + 256; } } /* insert the remainder of text */ if (i != FALSE && work_buf_len > 0) { strcpy(&ods.cur_l->text[ods.p_off], work_buf); work_buf = work_buf_begin; free(work_buf); if(FormatLines(ods.cur_l, empty, LINELEN(), headents[ods.cur_e].break_on_comma, 0) == -1) { i = FALSE; } else { len = 0; travel = ods.cur_l; while (len < work_buf_len + ods.p_off){ if(!tentative_cur_l){ if(tentative_p_off <= strlen(travel->text)) tentative_cur_l = travel; else tentative_p_off -= strlen(travel->text); } len += strlen(travel->text); if (len >= work_buf_len + ods.p_off) break; travel = travel->next; } ods.cur_l = travel; ods.p_off = strlen(travel->text) - len + work_buf_len + ods.p_off; if(tentative_cur_l && tentative_p_off >= 0 && tentative_p_off <= strlen(tentative_cur_l->text)){ ods.cur_l = tentative_cur_l; ods.p_off = tentative_p_off; } } } }else{ if(FormatLines(ods.cur_l, buf, LINELEN(), headents[ods.cur_e].break_on_comma, 0) == -1) i = FALSE; else{ i = TRUE; buf_len = strlen(buf); tentative_p_off = buf_len; ods.p_off = 0; len = 0; /* put cursor after the new text */ travel = ods.cur_l; while(len < buf_len){ if(!tentative_cur_l){ if(tentative_p_off <= strlen(travel->text)) tentative_cur_l = travel; else tentative_p_off -= strlen(travel->text); } len += strlen(travel->text); if(len >= buf_len) break; travel = travel->next; } if(tentative_cur_l && tentative_p_off >= 0 && tentative_p_off <= strlen(tentative_cur_l->text)){ ods.cur_l = tentative_cur_l; ods.p_off = tentative_p_off; } } } free(buf); return(i); } /* * break_point - Break the given line s at the most reasonable character c * within l max characters. * * returns: * Pointer to the best break point in s, or * Pointer to the beginning of s if no break point found */ char * break_point(s, l, ch, q) char *s; int l, ch, *q; { register char *b = s + l; int quoted = (q) ? *q : 0; while(b != s){ if(ch == ',' && *b == '"') /* don't break on quoted ',' */ quoted = !quoted; /* toggle quoted state */ if(*b == ch){ if(ch == ' '){ if(b + 1 < s + l){ b++; /* leave the ' ' */ break; } } else{ /* * if break char isn't a space, leave a space after * the break char. */ if(!(b+1 >= s+l || (b[1] == ' ' && b+2 == s+l))){ b += (b[1] == ' ') ? 2 : 1; break; } } } b--; } if(q) *q = quoted; return((quoted) ? s : b); } /* * hldelete() - remove the header line pointed to by l from the linked list * of lines. * * notes: * the case of first line in field is kind of bogus. since * the array of headers has a pointer to the first line, and * i don't want to worry about this too much, i just copied * the line below and removed it rather than the first one * from the list. * * returns: * TRUE if it worked * FALSE otherwise */ hldelete(l) struct hdr_line *l; { register struct hdr_line *lp; if(l == NULL) return(FALSE); if(l->next == NULL && l->prev == NULL){ /* only one line in field */ l->text[0] = '\0'; return(TRUE); /* no free only line in list */ } else if(l->next == NULL){ /* last line in field */ l->prev->next = NULL; } else if(l->prev == NULL){ /* first line in field */ strcpy(l->text, l->next->text); lp = l->next; if((l->next = lp->next) != NULL) l->next->prev = l; l = lp; } else{ /* some where in field */ l->prev->next = l->next; l->next->prev = l->prev; } l->next = NULL; l->prev = NULL; free((char *)l); return(TRUE); } /* * is_blank - returns true if the next n chars from coordinates row, col * on display are spaces */ is_blank(row, col, n) int row, col, n; { n += col; for( ;col < n; col++){ if(pscr(row, col) == NULL || pscr(row, col)->c != ' ') return(0); } return(1); } /* * ShowPrompt - display key help corresponding to the current header entry */ void ShowPrompt() { if(headents[ods.cur_e].key_label){ menu_header[TO_KEY].name = "^T"; menu_header[TO_KEY].label = headents[ods.cur_e].key_label; KS_OSDATASET(&menu_header[TO_KEY], KS_OSDATAGET(&headents[ods.cur_e])); } else menu_header[TO_KEY].name = NULL; if(Pmaster && Pmaster->exit_label) menu_header[SEND_KEY].label = Pmaster->exit_label; else if(gmode & (MDVIEW | MDHDRONLY)) menu_header[SEND_KEY].label = (gmode & MDHDRONLY) ? "eXit/Save" : "eXit"; else menu_header[SEND_KEY].label = "Send"; if(gmode & MDVIEW){ menu_header[CUT_KEY].name = NULL; menu_header[DEL_KEY].name = NULL; menu_header[UDEL_KEY].name = NULL; } else{ menu_header[CUT_KEY].name = "^K"; menu_header[DEL_KEY].name = "^D"; menu_header[UDEL_KEY].name = "^U"; } if(Pmaster->ctrlr_label){ menu_header[RICH_KEY].label = Pmaster->ctrlr_label; menu_header[RICH_KEY].name = "^R"; } else if(gmode & MDHDRONLY){ menu_header[RICH_KEY].name = NULL; } else{ menu_header[RICH_KEY].label = "Rich Hdr"; menu_header[RICH_KEY].name = "^R"; } if(gmode & MDHDRONLY){ if(headents[ods.cur_e].fileedit){ menu_header[PONE_KEY].name = "^_"; menu_header[PONE_KEY].label = "Edit File"; } else menu_header[PONE_KEY].name = NULL; menu_header[ATT_KEY].name = NULL; } else{ menu_header[PONE_KEY].name = "^O"; menu_header[PONE_KEY].label = "Postpone"; KS_OSDATASET(&menu_header[PONE_KEY],KS_OSDATAGET(&headents[ods.cur_e])); menu_header[ATT_KEY].name = "^J"; } wkeyhelp(menu_header); } /* * packheader - packup all of the header fields for return to caller. * NOTE: all of the header info passed in, including address * of the pointer to each string is contained in the * header entry array "headents". */ packheader() { register int i = 0; /* array index */ register int count; /* count of chars in a field */ register int retval = TRUE; /* count of chars in a field */ register char *bufp; /* */ register struct hdr_line *line; if(!headents) return(TRUE); while(headents[i].name != NULL){ #ifdef ATTACHMENTS /* * attachments are special case, already in struct we pass back */ if(headents[i].is_attach){ i++; continue; } #endif if(headents[i].blank){ i++; continue; } /* * count chars to see if we need a new malloc'd space for our * array. */ line = headents[i].hd_text; count = 0; while(line != NULL){ /* * add one for possible concatination of a ' ' character ... */ count += (strlen(line->text) + 1); line = line->next; } line = headents[i].hd_text; if(count < headents[i].maxlen){ *headents[i].realaddr[0] = '\0'; } else{ /* * don't forget to include space for the null terminator!!!! */ if((bufp = (char *)malloc((count+1) * sizeof(char))) != NULL){ *bufp = '\0'; free(*headents[i].realaddr); *headents[i].realaddr = bufp; } else{ emlwrite("Can't make room to pack header field.", NULL); retval = FALSE; } } if(retval != FALSE){ while(line != NULL){ /* pass the cursor offset back in Pmaster struct */ if(headents[i].start_here && ods.cur_l == line && Pmaster) Pmaster->edit_offset += strlen(*headents[i].realaddr); strcat(*headents[i].realaddr, line->text); if(line->text[0] && line->text[strlen(line->text)-1] == ',') strcat(*headents[i].realaddr, " "); line = line->next; } } i++; } return(retval); } /* * zotheader - free all malloc'd lines associated with the header structs */ void zotheader() { register struct headerentry *i; for(i = headents; headents && i->name; i++) zotentry(i->hd_text); } /* * zotentry - free malloc'd space associated with the given linked list */ void zotentry(l) register struct hdr_line *l; { register struct hdr_line *ld, *lf = l; while((ld = lf) != NULL){ lf = ld->next; ld->next = ld->prev = NULL; free((char *) ld); } } /* * zotcomma - blast any trailing commas and white space from the end * of the given line */ int zotcomma(s) char *s; { register char *p; int retval = FALSE; p = &s[strlen(s)]; while(--p >= s){ if(*p != ' '){ if(*p == ','){ *p = '\0'; retval = TRUE; } return(retval); } } return(retval); } /* * Save the current state of global variables so that we can restore * them later. This is so we can call pico again. * Also have to initialize some variables that normally would be set to * zero on startup. */ VARS_TO_SAVE * save_pico_state() { VARS_TO_SAVE *ret; extern int vtrow; extern int vtcol; extern int lbound; extern VIDEO **vscreen; extern VIDEO **pscreen; extern int pico_all_done; extern jmp_buf finstate; extern char *pico_anchor; if((ret = (VARS_TO_SAVE *)malloc(sizeof(VARS_TO_SAVE))) == NULL) return(ret); ret->vtrow = vtrow; ret->vtcol = vtcol; ret->lbound = lbound; ret->vscreen = vscreen; ret->pscreen = pscreen; ret->ods = ods; ret->delim_ps = delim_ps; ret->invert_ps = invert_ps; ret->pico_all_done = pico_all_done; memcpy(ret->finstate, finstate, sizeof(jmp_buf)); ret->pico_anchor = pico_anchor; ret->Pmaster = Pmaster; ret->fillcol = fillcol; if((ret->pat = (char *)malloc(sizeof(char) * (strlen(pat)+1))) != NULL) strcpy(ret->pat, pat); ret->ComposerTopLine = ComposerTopLine; ret->ComposerEditing = ComposerEditing; ret->gmode = gmode; ret->alt_speller = alt_speller; ret->quote_str = glo_quote_str; ret->currow = currow; ret->curcol = curcol; ret->thisflag = thisflag; ret->lastflag = lastflag; ret->curgoal = curgoal; ret->opertree = (char *) malloc(sizeof(char) * (strlen(opertree) + 1)); if(ret->opertree != NULL) strcpy(ret->opertree, opertree); ret->curwp = curwp; ret->wheadp = wheadp; ret->curbp = curbp; ret->bheadp = bheadp; ret->km_popped = km_popped; ret->mrow = term.t_mrow; /* Initialize for next pico call */ wheadp = NULL; curwp = NULL; bheadp = NULL; curbp = NULL; return(ret); } void restore_pico_state(state) VARS_TO_SAVE *state; { extern int vtrow; extern int vtcol; extern int lbound; extern VIDEO **vscreen; extern VIDEO **pscreen; extern int pico_all_done; extern jmp_buf finstate; extern char *pico_anchor; clearcursor(); vtrow = state->vtrow; vtcol = state->vtcol; lbound = state->lbound; vscreen = state->vscreen; pscreen = state->pscreen; ods = state->ods; delim_ps = state->delim_ps; invert_ps = state->invert_ps; pico_all_done = state->pico_all_done; memcpy(finstate, state->finstate, sizeof(jmp_buf)); pico_anchor = state->pico_anchor; Pmaster = state->Pmaster; if(Pmaster) headents = Pmaster->headents; fillcol = state->fillcol; if(state->pat) strcpy(pat, state->pat); ComposerTopLine = state->ComposerTopLine; ComposerEditing = state->ComposerEditing; gmode = state->gmode; alt_speller = state->alt_speller; glo_quote_str = state->quote_str; currow = state->currow; curcol = state->curcol; thisflag = state->thisflag; lastflag = state->lastflag; curgoal = state->curgoal; if(state->opertree) strcpy(opertree, state->opertree); curwp = state->curwp; wheadp = state->wheadp; curbp = state->curbp; bheadp = state->bheadp; km_popped = state->km_popped; term.t_mrow = state->mrow; } void free_pico_state(state) VARS_TO_SAVE *state; { if(state->pat) free(state->pat); if(state->opertree) free(state->opertree); free(state); } /* * Ok to call this twice in a row because it won't do anything the second * time. */ void fix_mangle_and_err(mangled, errmsg, name) int *mangled; char **errmsg; char *name; { if(mangled && *mangled){ ttresize(); picosigs(); PaintBody(0); *mangled = 0; } if(errmsg && *errmsg){ if(**errmsg){ char err[500]; sprintf(err, "%s field: %s", name, *errmsg); (*term.t_beep)(); emlwrite(err, NULL); } else mlerase(); free(*errmsg); *errmsg = NULL; } } #ifdef MOUSE #undef HeaderEditor /* * Wraper function for the real header editor. * Does the important tasks of: * 1) verifying that we _can_ edit the headers. * 2) acting on the result code from the header editor. */ int HeaderEditor(f, n) int f, n; { int retval; #ifdef _WINDOWS /* Sometimes we get here from a scroll callback, which * is no good at all because mswin is not ready to process input and * this _headeredit() will never do anything. * Putting this test here was the most general solution I could think * of. */ if (!mswin_caninput()) return (-1); #endif retval = HeaderEditorWork(f, n); if (retval == -3) { retval = mousepress(0,0); } return (retval); } #endif