#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: display.c 1025 2008-04-08 22:59:38Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2006-2008 University of Washington * Copyright 2013-2021 Eduardo Chappa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== * * Program: Display functions * */ /* * The functions in this file handle redisplay. There are two halves, the * ones that update the virtual display screen, and the ones that make the * physical display screen the same as the virtual display screen. These * functions use hints that are left in the windows by the commands. * */ #include "../c-client/mail.h" #include "../c-client/utf8.h" #ifdef _WINDOWS /* wingdi.h uses ERROR (!) and we aren't using the c-client ERROR so... */ #undef ERROR #endif #include "headers.h" #include "../pith/charconv/filesys.h" #include "../pith/charconv/utf8.h" void vtmove(int, int); void vtputc(CELL); void vteeol(void); void updateline(int, CELL *, CELL *, short *, int); void updatelinecolor(int, CELL *, CELL *, short *, int); void updext(void); void mlputi(int, int); void pprints(int, int); void mlputli(long, int); void showCompTitle(void); int nlforw(void); int dumbroot(int, int); int dumblroot(long, int); unsigned cellwidth_ptr_to_ptr(CELL *pstart, CELL *pend); unsigned vcellwidth_a_to_b(int row, int a, int b); int window_signature_block(WINDOW *wp); #ifdef _WINDOWS void pico_config_menu_items (KEYMENU *); int update_scroll (void); #endif /* _WINDOWS */ /* * Standard pico keymenus... */ static KEYMENU menu_pico[] = { {"^G", N_("Get Help"), KS_SCREENHELP}, {"^O", N_("WriteOut"), KS_SAVEFILE}, {"^R", N_("Read File"), KS_READFILE}, {"^Y", N_("Prev Pg"), KS_PREVPAGE}, {"^K", N_("Cut Text"), KS_NONE}, {"^C", N_("Cur Pos"), KS_CURPOSITION}, {"^X", N_("Exit"), KS_EXIT}, {"^J", N_("Justify"), KS_JUSTIFY}, {"^W", N_("Where is"), KS_WHEREIS}, {"^V", N_("Next Pg"), KS_NEXTPAGE}, {"^U", NULL, KS_NONE}, #ifdef SPELLER {"^T", N_("To Spell"), KS_SPELLCHK} #else {"^D", N_("Del Char"), KS_NONE} #endif }; #define UNCUT_KEY 10 static KEYMENU menu_compose[] = { {"^G", N_("Get Help"), KS_SCREENHELP}, {"^X", NULL, KS_SEND}, {"^R", N_("Read File"), KS_READFILE}, {"^Y", N_("Prev Pg"), KS_PREVPAGE}, {"^K", N_("Cut Text"), KS_NONE}, {"^O", N_("Postpone"), KS_POSTPONE}, /* TRANSLATORS: Justify is to reformat a paragraph automatically */ {"^C", N_("Cancel"), KS_CANCEL}, {"^J", N_("Justify"), KS_JUSTIFY}, {NULL, NULL, KS_NONE}, {"^V", N_("Next Pg"), KS_NEXTPAGE}, {"^U", NULL, KS_NONE}, #ifdef SPELLER {"^T", N_("To Spell"), KS_SPELLCHK} #else {"^D", N_("Del Char"), KS_NONE} #endif }; #define EXIT_KEY 1 #define PSTPN_KEY 5 #define WHERE_KEY 8 /* * Definition's for pico's modeline */ #define PICO_TITLE " UW PICO %s" #define PICO_MOD_MSG "Modified" #define PICO_NEWBUF_MSG "New Buffer" #define WFDEBUG 0 /* Window flag debug. */ #define VFCHG 0x0001 /* Changed flag */ #define VFEXT 0x0002 /* extended (beyond column 80) */ #define VFSIG 0x0004 /* in signature block */ int vtrow = 0; /* Row location of SW cursor */ int vtcol = 0; /* Column location of SW cursor */ int vtind = 0; /* Index into row array of SW cursor */ int ttrow = FARAWAY; /* Row location of HW cursor */ int ttcol = FARAWAY; /* Column location of HW cursor */ int lbound = 0; /* leftmost column of current line being displayed */ VIDEO **vscreen; /* Virtual screen. */ VIDEO **pscreen; /* Physical screen. */ /* * Initialize the data structures used by the display code. The edge vectors * used to access the screens are set up. The operating system's terminal I/O * channel is set up. All the other things get initialized at compile time. * The original window has "WFCHG" set, so that it will get completely * redrawn on the first call to "update". */ int vtinit(void) { int i, j; VIDEO *vp; CELL ac; ac.c = ' '; ac.a = 0; if(Pmaster == NULL) vtterminalinfo(gmode & MDTCAPWINS); (*term.t_open)(); (*term.t_rev)(FALSE); vscreen = (VIDEO **) malloc((term.t_nrow+1)*sizeof(VIDEO *)); memset(vscreen, 0, (term.t_nrow+1)*sizeof(VIDEO *)); if (vscreen == NULL){ emlwrite("Allocating memory for virtual display failed.", NULL); return(FALSE); } pscreen = (VIDEO **) malloc((term.t_nrow+1)*sizeof(VIDEO *)); memset(pscreen, 0, (term.t_nrow+1)*sizeof(VIDEO *)); if (pscreen == NULL){ free((void *)vscreen); emlwrite("Allocating memory for physical display failed.", NULL); return(FALSE); } for (i = 0; i <= term.t_nrow; ++i) { vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); if (vp == NULL){ free((void *)vscreen); free((void *)pscreen); emlwrite("Allocating memory for virtual display lines failed.", NULL); return(FALSE); } else for(j = 0; j < term.t_ncol; j++) vp->v_text[j] = ac; vp->v_flag = 0; vp->v_length = 0; vscreen[i] = vp; vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); if (vp == NULL){ free((void *)vscreen[i]); while(--i >= 0){ free((void *)vscreen[i]); free((void *)pscreen[i]); } free((void *)vscreen); free((void *)pscreen); emlwrite("Allocating memory for physical display lines failed.", NULL); return(FALSE); } else for(j = 0; j < term.t_ncol; j++) vp->v_text[j] = ac; vp->v_flag = 0; vp->v_length = 0; pscreen[i] = vp; } return(TRUE); } int vtterminalinfo(int termcap_wins) { return((term.t_terminalinfo) ? (*term.t_terminalinfo)(termcap_wins) : (Pmaster ? 0 : TRUE)); } /* * Clean up the virtual terminal system, in anticipation for a return to the * operating system. Move down to the last line and clear it out (the next * system prompt will be written in the line). Shut down the channel to the * terminal. */ void vttidy(void) { movecursor(term.t_nrow-1, 0); peeol(); movecursor(term.t_nrow, 0); peeol(); (*term.t_close)(); } /* * Set the virtual cursor to the specified row and column on the virtual * screen. There is no checking for nonsense values; this might be a good * idea during the early stages. */ void vtmove(int row, int col) { vtrow = row; vtcol = col; if(vtcol < 0) vtind = -1; else if(vtcol == 0) vtind = vtcol; else{ /* * This is unused so don't worry about it. */ assert(0); } } /* * Write a character to the virtual screen. The virtual row and column are * updated. If the line is too long put a "$" in the last column. This routine * only puts printing characters into the virtual terminal buffers. Only * column overflow is checked. */ void vtputc(CELL c) { VIDEO *vp; CELL ac; int w; vp = vscreen[vtrow]; ac.c = ' '; ac.a = c.a; ac.d = c.d; if (vtcol >= term.t_ncol) { /* * What's this supposed to be doing? This sets vtcol * to the even multiple of 8 >= vtcol. Why are we doing that? * Must make tab work correctly. * 24 -> 24 * 25 -> 32 * ... * 31 -> 32 * 32 -> 32 * 33 -> 40 */ vtcol = (vtcol + 0x07) & ~0x07; ac.c = '$'; /* * If we get to here that means that there must be characters * past the right hand edge, so we want to put a $ character * in the last visible character. It would be nice to replace * the last visible character by a double-$ if it is double-width * but we aren't doing that because we'd have to add up the widths * starting at the left hand margin each time through. */ if(vtind > 0 && vtind <= term.t_ncol) vp->v_text[vtind-1] = ac; } else if (c.c == '\t') { do { vtputc(ac); } while (((vtcol + (vtrow==currow ? lbound : 0)) & 0x07) != 0 && vtcol < term.t_ncol); } else if (ISCONTROL(c.c)){ ac.c = '^'; vtputc(ac); ac.c = ((c.c & 0x7f) | 0x40); vtputc(ac); } else{ /* * Have to worry about what happens if we skip over 0 * with a double-width character. There may be a better * place to be setting vtind, or maybe we could make do * without it. */ w = wcellwidth((UCS) c.c); w = (w >= 0 ? w : 1); if(vtcol == 0 || (vtcol < 0 && vtcol + w == 1)){ vtind = 0; if(vtcol < 0) vtcol = 0; } /* * Double-width character overlaps right edge. * Replace it with a $. */ if(vtcol + w > term.t_ncol){ ac.c = '$'; c = ac; w = 1; } if(vtind >= 0 && vtind < term.t_ncol) vp->v_text[vtind++] = c; vtcol += w; } } /* * Erase from the end of the software cursor to the end of the line on which * the software cursor is located. */ void vteeol(void) { register VIDEO *vp; CELL c; c.c = ' '; c.a = 0; vp = vscreen[vtrow]; if(vtind >= 0) while (vtind < term.t_ncol) vp->v_text[vtind++] = c; vtcol = term.t_ncol; } int window_signature_block(WINDOW *wp) { LINE *lp, *llp; int in_sig, is_sig_start = 0; int change = 0; llp = wp->w_linep; lp = lforw(wp->w_bufp->b_linep); do { in_sig = lback(lp) == wp->w_bufp->b_linep ? 0 : lback(lp)->l_sig; if(in_sig == 0){ if(llength(lp) == 3){ if(lgetc(lp, 0).c == '-' && lgetc(lp, 1).c == '-' && lgetc(lp, 2).c == ' '){ in_sig = 1; is_sig_start = 1; } } } else { if(lisblank(lp)) if(is_sig_start == 0) in_sig = 0; is_sig_start = 0; } if(lp->l_sig != in_sig) change++; lp->l_sig = in_sig; lp = lforw(lp); } while(lp != wp->w_bufp->b_linep); wp->w_linep = llp; return change; } /* * Make sure that the display is right. This is a three part process. First, * scan through all of the windows looking for dirty ones. Check the framing, * and refresh the screen. Second, make sure that "currow" and "curcol" are * correct for the current window. Third, make the virtual and physical * screens the same. */ void update(void) { LINE *lp; WINDOW *wp; VIDEO *vp1; VIDEO *vp2; int i; int j; int scroll = 0; int repaint= 0; CELL c; PCOLORS *pcolors = Pmaster && Pmaster->colors ? Pmaster->colors : Pcolors; #if TYPEAH if (typahead()) return; #endif #ifdef _WINDOWS /* This tells our MS Windows module to not bother updating the * cursor position while a massive screen update is in progress. */ mswin_beginupdate (); #endif /* * BUG: setting and unsetting whole region at a time is dumb. fix this. */ if(curwp->w_markp){ unmarkbuffer(); markregion(1); } wp = wheadp; while (wp != NULL){ /* Look at any window with update flags set on. */ if(pcolors && (repaint = window_signature_block(wp))){ sgarbf = TRUE; wp->w_flag |= WFEDIT | WFHARD; } if (wp->w_flag != 0){ /* If not force reframe, check the framing. */ if ((wp->w_flag & WFFORCE) == 0){ lp = wp->w_linep; for (i = 0; i < wp->w_ntrows; ++i){ if (lp == wp->w_dotp) goto out; if (lp == wp->w_bufp->b_linep) break; lp = lforw(lp); } } /* Not acceptable, better compute a new value for the line at the * top of the window. Then set the "WFHARD" flag to force full * redraw. */ i = wp->w_force; if (i > 0){ --i; if (i >= wp->w_ntrows) i = wp->w_ntrows-1; } else if (i < 0){ i += wp->w_ntrows; if (i < 0) i = 0; } else if(TERM_OPTIMIZE){ /* * find dotp, if its been moved just above or below the * window, use scrollxxx() to facilitate quick redisplay... */ lp = lforw(wp->w_dotp); if(lp != wp->w_dotp){ if(lp == wp->w_linep && lp != wp->w_bufp->b_linep){ scroll = 1; } else { lp = wp->w_linep; for(j=0;j < wp->w_ntrows; ++j){ if(lp != wp->w_bufp->b_linep) lp = lforw(lp); else break; } if(lp == wp->w_dotp && j == wp->w_ntrows) scroll = 2; } } j = i = wp->w_ntrows/2; } else i = wp->w_ntrows/2; lp = wp->w_dotp; while (i != 0 && lback(lp) != wp->w_bufp->b_linep){ --i; lp = lback(lp); } /* * this is supposed to speed things up by using tcap sequences * to efficiently scroll the terminal screen. the thinking here * is that its much faster to update pscreen[] than to actually * write the stuff to the screen... */ if(TERM_OPTIMIZE){ switch(scroll){ case 1: /* scroll text down */ j = j-i+1; /* add one for dot line */ /* * do we scroll down the header as well? Well, only * if we're not editing the header, we've backed up * to the top, and the composer is not being * displayed... */ if(Pmaster && Pmaster->headents && !ComposerEditing && (lback(lp) == wp->w_bufp->b_linep) && (ComposerTopLine == COMPOSER_TOP_LINE)) j += entry_line(1000, TRUE); /* Never > 1000 headers */ scrolldown(wp, -1, j); break; case 2: /* scroll text up */ j = wp->w_ntrows - (j-i); /* we chose new top line! */ if(Pmaster && j){ /* * do we scroll down the header as well? Well, only * if we're not editing the header, we've backed up * to the top, and the composer is not being * displayed... */ if(!ComposerEditing && (ComposerTopLine != COMPOSER_TOP_LINE)) scrollup(wp, COMPOSER_TOP_LINE, j+entry_line(1000, TRUE)); else scrollup(wp, -1, j); } else scrollup(wp, -1, j); break; default : break; } } wp->w_linep = lp; wp->w_flag |= WFHARD; /* Force full. */ out: /* * if the line at the top of the page is the top line * in the body, show the header... */ if(Pmaster && Pmaster->headents && !ComposerEditing){ if(lback(wp->w_linep) == wp->w_bufp->b_linep){ if(ComposerTopLine == COMPOSER_TOP_LINE){ i = term.t_nrow - 2 - term.t_mrow - HeaderLen(); if(i > 0 && nlforw() >= i) { /* room for header ? */ if((i = nlforw()/2) == 0 && term.t_nrow&1) i = 1; while(wp->w_linep != wp->w_bufp->b_linep && i--) wp->w_linep = lforw(wp->w_linep); } else ToggleHeader(1); } } else{ if(ComposerTopLine != COMPOSER_TOP_LINE) ToggleHeader(0); /* hide it ! */ } } /* Try to use reduced update. Mode line update has its own special * flag. The fast update is used if the only thing to do is within * the line editing. */ lp = wp->w_linep; i = wp->w_toprow; if ((wp->w_flag & ~WFMODE) == WFEDIT){ while (lp != wp->w_dotp){ ++i; lp = lforw(lp); } vscreen[i]->v_flag |= (lp->l_sig ? VFSIG : 0)| VFCHG; /* compute physical length of line in screen */ vscreen[i]->v_length = 0; for (j = 0; vscreen[i]->v_length < term.t_ncol && j < llength(lp); ++j){ c = lgetc(lp, j); if(c.c == '\t'){ vscreen[i]->v_length |= 0x07; vscreen[i]->v_length++; } else if(ISCONTROL(c.c)){ vscreen[i]->v_length += 2; } else{ int w; w = wcellwidth((UCS) c.c); vscreen[i]->v_length += (w >= 0 ? w : 1); } } vtmove(i, 0); for (j = 0; j < llength(lp); ++j) vtputc(lgetc(lp, j)); vteeol(); } else if ((wp->w_flag & (WFEDIT | WFHARD)) != 0){ while (i < wp->w_toprow+wp->w_ntrows){ vscreen[i]->v_flag |= (lp->l_sig ? VFSIG : 0 )| VFCHG; /* compute physical length of line in screen */ vscreen[i]->v_length = 0; for (j = 0; vscreen[i]->v_length < term.t_ncol && j < llength(lp); ++j){ c = lgetc(lp, j); if(c.c == '\t'){ vscreen[i]->v_length |= 0x07; vscreen[i]->v_length++; } else if(ISCONTROL(c.c)){ vscreen[i]->v_length += 2; } else{ int w; w = wcellwidth((UCS) c.c); vscreen[i]->v_length += (w >= 0 ? w : 1); } } vtmove(i, 0); /* if line has been changed */ if (lp != wp->w_bufp->b_linep){ for (j = 0; j < llength(lp); ++j) vtputc(lgetc(lp, j)); lp = lforw(lp); } vteeol(); ++i; } } #if ~WFDEBUG if ((wp->w_flag&WFMODE) != 0) modeline(wp); wp->w_flag = 0; wp->w_force = 0; #endif } #if WFDEBUG modeline(wp); wp->w_flag = 0; wp->w_force = 0; #endif /* and onward to the next window */ wp = wp->w_wndp; } /* Always recompute the row and column number of the hardware cursor. This * is the only update for simple moves. */ lp = curwp->w_linep; currow = curwp->w_toprow; while (lp != curwp->w_dotp){ ++currow; lp = lforw(lp); } curcol = 0; i = 0; while (i < curwp->w_doto){ c = lgetc(lp, i++); if(c.c == '\t'){ curcol |= 0x07; ++curcol; } else if(ISCONTROL(c.c)){ curcol += 2; } else{ int w; w = wcellwidth((UCS) c.c); curcol += (w >= 0 ? w : 1); } } if (curcol >= term.t_ncol) { /* extended line. */ /* flag we are extended and changed */ vscreen[currow]->v_flag |= VFEXT | VFCHG; updext(); /* and output extended line */ } else lbound = 0; /* not extended line */ /* make sure no lines need to be de-extended because the cursor is * no longer on them */ wp = wheadp; while (wp != NULL) { lp = wp->w_linep; i = wp->w_toprow; while (i < wp->w_toprow + wp->w_ntrows) { if (vscreen[i]->v_flag & VFEXT) { /* always flag extended lines as changed */ vscreen[i]->v_flag |= VFCHG; if ((wp != curwp) || (lp != wp->w_dotp) || (curcol < term.t_ncol)) { vtmove(i, 0); for (j = 0; j < llength(lp); ++j) vtputc(lgetc(lp, j)); vteeol(); /* this line no longer is extended */ vscreen[i]->v_flag &= ~VFEXT; } } lp = lforw(lp); ++i; } /* and onward to the next window */ wp = wp->w_wndp; } /* Special hacking if the screen is garbage. Clear the hardware screen, * and update your copy to agree with it. Set all the virtual screen * change bits, to force a full update. */ if (sgarbf != FALSE){ if(Pmaster){ int rv; showCompTitle(); if(ComposerTopLine != COMPOSER_TOP_LINE){ UpdateHeader(0); /* arrange things */ PaintHeader(COMPOSER_TOP_LINE, TRUE); } /* * since we're using only a portion of the screen and only * one buffer, only clear enough screen for the current window * which is to say the *only* window. */ for(i=wheadp->w_toprow;i<=term.t_nrow; i++){ movecursor(i, 0); peeol(); vscreen[i]->v_flag |= VFCHG; } rv = (*Pmaster->showmsg)('X' & 0x1f); /* ctrl-L */ ttresize(); picosigs(); /* restore altered handlers */ if(rv) /* Did showmsg corrupt the display? */ PaintBody(0); /* Yes, repaint */ movecursor(wheadp->w_toprow, 0); } else{ c.c = ' '; c.a = 0; for (i = 0; i < term.t_nrow-term.t_mrow; i++){ vscreen[i]->v_flag |= VFCHG; vp1 = pscreen[i]; for (j = 0; j < term.t_ncol; j++) vp1->v_text[j] = c; if(sgarbf == FALSE){ movecursor(i, 0); term.t_eeol(); } } if(sgarbf != FALSE){ movecursor(0, 0); /* Erase the screen. */ (*term.t_eeop)(); } } sgarbf = FALSE; /* Erase-page clears */ mpresf = FALSE; /* the message area. */ if(Pmaster) modeline(curwp); else sgarbk = TRUE; /* fix the keyhelp as well...*/ } /* Make sure that the physical and virtual displays agree. Unlike before, * the "updateline" code is only called with a line that has been updated * for sure. */ if(Pmaster) i = curwp->w_toprow; else i = 0; if (term.t_nrow > term.t_mrow) c.c = term.t_nrow - term.t_mrow; else c.c = 0; for (; i < (int)c.c; ++i){ vp1 = vscreen[i]; /* for each line that needs to be updated, or that needs its reverse video status changed, call the line updater */ j = vp1->v_flag; if (j & VFCHG){ #if TYPEAH if (typahead()){ #ifdef _WINDOWS mswin_endupdate (); #endif return; } #endif vp2 = pscreen[i]; updateline(i, &vp1->v_text[0], &vp2->v_text[0], &vp1->v_flag, vp1->v_length); } } if(Pmaster == NULL){ if(sgarbk != FALSE){ if(term.t_mrow > 0){ movecursor(term.t_nrow-1, 0); peeol(); movecursor(term.t_nrow, 0); peeol(); } if(lastflag&CFFILL){ /* TRANSLATORS: UnJustify means undo the previous Justify command. */ menu_pico[UNCUT_KEY].label = N_("UnJustify"); if(!(lastflag&CFFLBF)){ emlwrite(_("Can now UnJustify!"), NULL); mpresf = FARAWAY; /* remove this after next keystroke! */ } } else menu_pico[UNCUT_KEY].label = N_("UnCut Text"); wkeyhelp(menu_pico); sgarbk = FALSE; } } if(lastflag&CFFLBF){ emlwrite(_("Can now UnJustify!"), NULL); mpresf = FARAWAY; /* remove this after next keystroke! */ } /* Finally, update the hardware cursor and flush out buffers. */ movecursor(currow, curcol - lbound); #ifdef _WINDOWS mswin_endupdate (); /* * Update the scroll bars. This function is where curbp->b_linecnt * is really managed. See update_scroll. */ update_scroll (); #endif (*term.t_flush)(); } /* updext - update the extended line which the cursor is currently * on at a column greater than the terminal width. The line * will be scrolled right or left to let the user see where * the cursor is */ void updext(void) { int rcursor; /* real cursor location */ LINE *lp; /* pointer to current line */ int j; /* index into line */ int w = 0; int ww; /* * Calculate what column the real cursor will end up in. * The cursor will be in the rcursor'th column. So if we're * counting columns 0 1 2 3 and rcursor is 8, then rcursor * will be over cell 7. * * What this effectively does is to scroll the screen as we're * moving to the right when the cursor first passes off the * screen's right edge. It would be nice if it did the same * thing coming back to the left. Instead, in order that the * screen's display depends only on the curcol and not on * how we got there, the screen scrolls when we pass the * t_margin column. It's also kind of funky that you can't * see the character under the $ but you can delete it. */ rcursor = ((curcol - term.t_ncol) % (term.t_ncol - term.t_margin + 1)) + term.t_margin; lbound = curcol - rcursor + 1; /* * Make sure lbound is set so that a double-width character does * not straddle the boundary. If it does, move over one cell. */ lp = curwp->w_dotp; /* line to output */ for (j=0; j= 0 ? ww : 1); } if(w > lbound) lbound = w; /* scan through the line outputting characters to the virtual screen * once we reach the left edge */ vtmove(currow, -lbound); /* start scanning offscreen */ for (j=0; jv_text[0].c); vscreen[currow]->v_text[0].c = '$'; vscreen[currow]->v_text[0].a = 0; if(w == 2){ /* * We want to put $ in the first two columns so that it * takes up the right amount of space, but that means we * have to scoot the real characters over one slot. */ for (j = term.t_ncol-1; j >= 2; --j) vscreen[currow]->v_text[j] = vscreen[currow]->v_text[j-1]; vscreen[currow]->v_text[1].c = '$'; vscreen[currow]->v_text[1].a = 0; } } /* update line color, to be executed to update lines when color is on */ void updatelinecolor (int row, CELL vline[], CELL pline[], short *flags, int len) { CELL *cp1, *cp2, *cp3, *cp4, *cp5; int nbflag; /* non-blanks to the right flag? */ int cleartoeol = 0; int in_quote, level; PCOLORS *pcolors = Pmaster && Pmaster->colors ? Pmaster->colors : Pcolors; COLOR_PAIR *lastc = NULL, *pcolor = NULL; int first = 1, lastattr = -1, change = 0; if(pcolors == NULL){ updateline(row, vline, pline, flags, len); return; } nbflag = FALSE; lastc = pico_get_cur_color(); /* set up pointers to virtual and physical lines */ cp1 = &vline[0]; cp2 = &pline[0]; cp3 = &vline[term.t_ncol]; cp4 = &pline[term.t_ncol]; if(cellwidth_ptr_to_ptr(cp1, cp3) == cellwidth_ptr_to_ptr(cp2, cp4)) while (cp3 != cp1 && cp3[-1].c == cp4[-1].c && cp3[-1].a == cp4[-1].a) { --cp3; --cp4; if (cp3[0].c != ' ' || cp3[0].a != 0) /* Note if any nonblank */ nbflag = TRUE; /* in right match. */ } cp5 = cp3; if (nbflag == FALSE && TERM_EOLEXIST) { /* Erase to EOL ? */ while (cp5 != cp1 && cp5[-1].c == ' ' && cp5[-1].a == 0) --cp5; if (cp3-cp5 <= 3) /* Use only if erase is */ cp5 = cp3; /* fewer characters. */ } /* go to start of line */ movecursor(row, 0); if(cp1 != cp5){ int w1, w2; w1 = cellwidth_ptr_to_ptr(cp1, cp3); w2 = cellwidth_ptr_to_ptr(cp2, cp2 + (cp3-cp1)); if(w1 < w2 || (nbflag && w1 != w2)){ if(TERM_EOLEXIST){ if(nbflag){ /* * Draw all of the characters starting with cp1 * until we get to all spaces, then clear to the end of * line from there. Watch out we don't run over the * right hand edge, which shouldn't happen. * * Set cp5 to the first of the repeating spaces. */ cp5 = &vline[term.t_ncol]; while (cp5 != cp1 && cp5[-1].c == ' ' && cp5[-1].a == 0) --cp5; } /* * In the !nbflag case we want spaces from cp5 on. * Setting cp3 to something different from cp5 triggers * the clear to end of line below. */ if(cellwidth_ptr_to_ptr(&vline[0], cp5) < term.t_ncol) cleartoeol++; } else{ int w; /* * No peeol so draw all the way to the edge whether they * are spaces or not. */ cp3 = &vline[0]; for(w = 0; w < term.t_ncol; cp3++){ int ww; ww = wcellwidth((UCS) cp3->c); w += (ww >= 0 ? ww : 1); } cp5 = cp3; } } } if(row != 0 && len < term.t_ncol) cp5 = cp1 + len; in_quote = 1; level = -1; while (cp1 != cp5){ /* Ordinary. */ int ww; if(lastattr < 0){ lastattr = cp1->a; change = 0; } else change = lastattr != cp1->a; if(first != 0){ first = 0; if(row == 0) pico_set_colorp(pcolors->tbcp, PSC_NONE); else if(row < term.t_nrow - 2) pcolor = (*flags & VFSIG) ? pcolors->sbcp : pcolors->ntcp; } if(cp1->c != '>' && cp1->c != ' ') in_quote = 0; else if (in_quote && cp1->c == '>' && pcolors != NULL) level = (level + 1) % 3; if(level >= 0){ if(level == 0) pcolor = pcolors->qlcp; else if(level == 1) pcolor = pcolors->qllcp; else if(level == 2) pcolor = pcolors->qlllcp; } if(cp1->a == 1) pcolor = pcolors->rtcp; /* pcolor = proposed color */ if(change == 0) pico_set_colorp(pcolor, PSC_NONE); else (*term.t_rev)(cp1->a); /* set inverse for this char */ if(change == 0) (*term.t_rev)(cp1->a); /* set inverse for this char */ (*term.t_putchar)(cp1->c); ww = wcellwidth((UCS) cp1->c); ttcol += (ww >= 0 ? ww : 1); *cp2++ = *cp1++; } if (lastc){ (void)pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } (*term.t_rev)(0); /* turn off inverse anyway! */ if (cp5 != cp3 || cleartoeol) /* Erase. */ peeol(); *flags &= ~(VFCHG|VFSIG); /* flag this line is changed */ } /* * Update a single line. This does not know how to use insert or delete * character sequences; we are using VT52 functionality. Update the physical * row and column variables. */ void updateline(int row, /* row on screen */ CELL vline[], /* what we want it to end up as */ CELL pline[], /* what it looks like now */ short *flags, int len) { CELL *cp1, *cp2, *cp3, *cp4, *cp5, *cp6, *cp7; int display = TRUE; int nbflag; /* non-blanks to the right flag? */ int cleartoeol = 0; if(row < 0 || row > term.t_nrow) return; if((Pmaster && Pmaster->colors) || Pcolors){ updatelinecolor(row, vline, pline, flags, len); return; } /* set up pointers to virtual and physical lines */ cp1 = &vline[0]; cp2 = &pline[0]; cp3 = &vline[term.t_ncol]; /* advance past any common chars at the left */ while (cp1 != cp3 && cp1[0].c == cp2[0].c && cp1[0].a == cp2[0].a) { ++cp1; ++cp2; } /* This can still happen, even though we only call this routine on changed * lines. A hard update is always done when a line splits, a massive * change is done, or a buffer is displayed twice. This optimizes out most * of the excess updating. A lot of computes are used, but these tend to * be hard operations that do a lot of update, so I don't really care. */ /* if both lines are the same, no update needs to be done */ if (cp1 == cp3){ *flags &= ~VFCHG; /* mark it clean */ return; } /* find out if there is a match on the right */ nbflag = FALSE; cp3 = &vline[term.t_ncol]; cp4 = &pline[term.t_ncol]; if(cellwidth_ptr_to_ptr(cp1, cp3) == cellwidth_ptr_to_ptr(cp2, cp4)) while (cp3[-1].c == cp4[-1].c && cp3[-1].a == cp4[-1].a) { --cp3; --cp4; if (cp3[0].c != ' ' || cp3[0].a != 0) /* Note if any nonblank */ nbflag = TRUE; /* in right match. */ } cp5 = cp3; if (nbflag == FALSE && TERM_EOLEXIST) { /* Erase to EOL ? */ while (cp5 != cp1 && cp5[-1].c == ' ' && cp5[-1].a == 0) --cp5; if (cp3-cp5 <= 3) /* Use only if erase is */ cp5 = cp3; /* fewer characters. */ } /* go to start of differences */ movecursor(row, cellwidth_ptr_to_ptr(&vline[0], cp1)); if (!nbflag) { /* use insert or del char? */ cp6 = cp3; cp7 = cp4; if(TERM_INSCHAR &&(cp7!=cp2 && cp6[0].c==cp7[-1].c && cp6[0].a==cp7[-1].a)){ while (cp7 != cp2 && cp6[0].c==cp7[-1].c && cp6[0].a==cp7[-1].a){ --cp7; --cp6; } if (cp7==cp2 && cp4-cp2 > 3){ int ww; (*term.t_rev)(cp1->a); /* set inverse for this char */ o_insert((UCS) cp1->c); /* insert the char */ ww = wcellwidth((UCS) cp1->c); ttcol += (ww >= 0 ? ww : 1); display = FALSE; /* only do it once!! */ } } else if(TERM_DELCHAR && cp3 != cp1 && cp7[0].c == cp6[-1].c && cp7[0].a == cp6[-1].a){ while (cp6 != cp1 && cp7[0].c==cp6[-1].c && cp7[0].a==cp6[-1].a){ --cp7; --cp6; } if (cp6==cp1 && cp5-cp6 > 3){ int w; w = wcellwidth((UCS) cp7[0].c); w = (w >= 0 ? w : 1); while(w-- > 0) /* in case double-width char */ o_delete(); /* delete the char */ display = FALSE; /* only do it once!! */ } } } if(cp1 != cp5 && display){ int w1, w2; /* * If we need to copy characters from cp1 to cp2 and * we need to display them, then we have to worry about * the characters that we are replacing being of a different * width than the new characters, else the display may be * messed up. * * If the new width (w1) is less than the old width, that means * we will leave behind some old remnants if we aren't careful. * If the new width is larger than the old width, we have to * make sure we draw the characters all the way to the end * in order to get it right. Take advantage of clear to end * of line if we have it. */ w1 = cellwidth_ptr_to_ptr(cp1, cp3); w2 = cellwidth_ptr_to_ptr(cp2, cp2 + (cp3-cp1)); if(w1 < w2 || (nbflag && w1 != w2)){ if(TERM_EOLEXIST){ if(nbflag){ /* * Draw all of the characters starting with cp1 * until we get to all spaces, then clear to the end of * line from there. Watch out we don't run over the * right hand edge, which shouldn't happen. * * Set cp5 to the first of the repeating spaces. */ cp5 = &vline[term.t_ncol]; while (cp5 != cp1 && cp5[-1].c == ' ' && cp5[-1].a == 0) --cp5; } /* * In the !nbflag case we want spaces from cp5 on. * Setting cp3 to something different from cp5 triggers * the clear to end of line below. */ if(cellwidth_ptr_to_ptr(&vline[0], cp5) < term.t_ncol) cleartoeol++; } else{ int w; /* * No peeol so draw all the way to the edge whether they * are spaces or not. */ cp3 = &vline[0]; for(w = 0; w < term.t_ncol; cp3++){ int ww; ww = wcellwidth((UCS) cp3->c); w += (ww >= 0 ? ww : 1); } cp5 = cp3; } } } while (cp1 != cp5) { /* Ordinary. */ int ww; if(display){ (*term.t_rev)(cp1->a); /* set inverse for this char */ (*term.t_putchar)(cp1->c); } ww = wcellwidth((UCS) cp1->c); ttcol += (ww >= 0 ? ww : 1); *cp2++ = *cp1++; } (*term.t_rev)(0); /* turn off inverse anyway! */ if (cp5 != cp3 || cleartoeol) { /* Erase. */ if(display) peeol(); else while (cp1 != cp3) *cp2++ = *cp1++; } *flags &= ~VFCHG; /* flag this line is changed */ } /* * Redisplay the mode line for the window pointed to by the "wp". This is the * only routine that has any idea of how the modeline is formatted. You can * change the modeline format by hacking at this routine. Called by "update" * any time there is a dirty window. */ void modeline(WINDOW *wp) { if(Pmaster){ if(ComposerEditing) ShowPrompt(); else{ menu_compose[EXIT_KEY].label = (Pmaster->headents) ? N_("Send") :N_("Exit"); menu_compose[PSTPN_KEY].name = (Pmaster->headents) ? "^O" : NULL; menu_compose[PSTPN_KEY].label = (Pmaster->headents) ? N_("Postpone") : NULL; menu_compose[WHERE_KEY].name = (Pmaster->alt_ed) ? "^_" : "^W"; menu_compose[WHERE_KEY].label = (Pmaster->alt_ed) ? N_("Alt Edit") : N_("Where is"); KS_OSDATASET(&menu_compose[WHERE_KEY], (Pmaster->alt_ed) ? KS_ALTEDITOR : KS_WHEREIS); menu_compose[UNCUT_KEY].label = (thisflag&CFFILL) ? N_("UnJustify") : N_("UnCut Text"); wkeyhelp(menu_compose); } } else{ BUFFER *bp; char t1[NLINE], t2[NLINE], t3[NLINE], tline[NLINE]; int w1, w2, w3, w1_to_2, w2_to_3, w3_to_r; UCS *ucs; vtmove(1, 0); vteeol(); vscreen[0]->v_flag |= VFCHG; /* Redraw next time. */ vtmove(0, 0); /* Seek to right line. */ snprintf(t1, sizeof(t1), PICO_TITLE, version); /* write version */ bp = wp->w_bufp; if(bp->b_fname[0]) /* File name? */ snprintf(t2, sizeof(t2), "File: %.*s", (int) sizeof(t2) - 7, bp->b_fname); else{ strncpy(t2, PICO_NEWBUF_MSG, sizeof(t2)); t2[sizeof(t2)-1] = '\0'; } if(bp->b_flag&BFCHG){ /* "MOD" if changed. */ strncpy(t3, PICO_MOD_MSG, sizeof(t3)); t3[sizeof(t3)-1] = '\0'; } else t3[0] = '\0'; #define ALLOFTHEM (w1+w1_to_2+w2+w2_to_3+w3+w3_to_r) #define ALLBUTSPACE (w1+w2+w3+w3_to_r) w1 = utf8_width(t1); w2 = utf8_width(t2); w3 = utf8_width(t3); w1_to_2 = w2_to_3 = 1; /* min values for separation */ w3_to_r = 2; if(ALLOFTHEM <= term.t_ncol){ /* everything fits */ w1_to_2 = (term.t_ncol - ALLBUTSPACE)/2; w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); } else{ w1 = 2; w1_to_2 = 0; if(ALLOFTHEM <= term.t_ncol) w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); else{ w1 = w1_to_2 = w3_to_r = 0; if(ALLOFTHEM <= term.t_ncol) w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); else{ if(bp->b_fname[0]){ snprintf(t2, sizeof(t2), "%.*s", (int) sizeof(t2) - 1, bp->b_fname); w2 = utf8_width(t2); } if(ALLOFTHEM <= term.t_ncol) w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); else{ w2 = 8; if(bp->b_fname[0] && ALLOFTHEM <= term.t_ncol){ /* reduce size of file */ w2 = term.t_ncol - (ALLOFTHEM - w2); t2[0] = t2[1] = t2[2] = '.'; utf8_to_width_rhs(t2+3, bp->b_fname, sizeof(t2)-3, w2-3); w2 = utf8_width(t2); } else w2 = utf8_width(t2); if(ALLOFTHEM <= term.t_ncol) w2_to_3 = term.t_ncol - (ALLBUTSPACE + w1_to_2); else{ w1 = w1_to_2 = w2 = w2_to_3 = w3_to_r = 0; if(ALLOFTHEM <= term.t_ncol) w2_to_3 = term.t_ncol - ALLBUTSPACE; else w3 = 0; } } } } } utf8_snprintf(tline, sizeof(tline), "%*.*w%*.*w%*.*w%*.*w%*.*w%*.*w", w1, w1, t1, w1_to_2, w1_to_2, "", w2, w2, t2, w2_to_3, w2_to_3, "", w3, w3, t3, w3_to_r, w3_to_r, ""); ucs = NULL; if(utf8_width(tline) <= term.t_ncol) ucs = utf8_to_ucs4_cpystr(tline); if(ucs){ UCS *ucsp; CELL c; c.a = 1; ucsp = ucs; while((c.c = CELLMASK & *ucsp++)) vtputc(c); fs_give((void **) &ucs); } } } /* * Send a command to the terminal to move the hardware cursor to row "row" * and column "col". The row and column arguments are origin 0. Optimize out * random calls. Update "ttrow" and "ttcol". */ void movecursor(int row, int col) { if (row!=ttrow || col!=ttcol) { ttrow = row; ttcol = col; (*term.t_move)(MIN(MAX(row,0),term.t_nrow), MIN(MAX(col,0),term.t_ncol-1)); } } /* * Erase any sense we have of the cursor's HW location... */ void clearcursor(void) { ttrow = ttcol = FARAWAY; } void get_cursor(int *row, int *col) { if(row) *row = ttrow; if(col) *col = ttcol; } /* * Erase the message line. This is a special routine because the message line * is not considered to be part of the virtual screen. It always works * immediately; the terminal buffer is flushed via a call to the flusher. */ void mlerase(void) { if (term.t_nrow < term.t_mrow) return; movecursor(term.t_nrow - term.t_mrow, 0); (*term.t_rev)(0); if (TERM_EOLEXIST == TRUE) peeol(); else{ if(ttrow == term.t_nrow){ while(ttcol++ < term.t_ncol-1) (*term.t_putchar)(' '); } else{ while(ttcol++ < term.t_ncol) /* track's ttcol */ (*term.t_putchar)(' '); } } (*term.t_flush)(); mpresf = FALSE; } /* returns the chosen dictionary. If one was already chosen * return that one */ char * speller_choice(char **sp_list, int *choice) { int ch_dict = -1; int cnt; if(sp_list == NULL || sp_list[0] == NULL || sp_list[0][0] == '\0') return NULL; if(choice && *choice >= 0) return sp_list[*choice]; for(cnt = 0; sp_list[cnt] != NULL && sp_list[cnt][0] != '\0'; cnt++) ; if(cnt > 10) /* only the first 10 dictionaries */ cnt = 10; if(cnt == 1) /* only one dictionary? choose it! */ ch_dict = 0; if(ch_dict > cnt - 1) /* choose again in case something changed */ ch_dict = -1; if(ch_dict < 0){ /* not a choice yet? do one now! */ int i; UCS *ucs4_prompt; EXTRAKEYS menu_dictionary[] = { {"0", NULL, '0'}, {"1", NULL, '1'}, {"2", NULL, '2'}, {"3", NULL, '3'}, {"4", NULL, '4'}, {"5", NULL, '5'}, {"6", NULL, '6'}, {"7", NULL, '7'}, {"8", NULL, '8'}, {"9", NULL, '9'} }; for(i = 0; i < cnt; i++) menu_dictionary[i].label = sp_list[i]; if(cnt < 10) menu_dictionary[cnt].name = NULL; /* write the prompt in utf8, and let internal functions translate it to ucs4 */ ucs4_prompt = utf8_to_ucs4_cpystr(_("Choose Dictionary: ")); i = mlchoose(ucs4_prompt, menu_dictionary); if(i >= '0' && i <= '9') ch_dict = i - '0'; if (i == -2) /* user cancelled */ ch_dict = -2; if(ucs4_prompt) fs_give((void **)&ucs4_prompt); } else ch_dict = -1; if(choice) *choice = ch_dict; return ch_dict >= 0 ? sp_list[ch_dict] : NULL; } /* just like mlreplyd, but user cannot fill a prompt */ int mlchoose(UCS *prompt, EXTRAKEYS *extras) { UCS c; UCS buf[NLINE]; int i; int return_val = 0; KEYMENU menu_choose[12]; COLOR_PAIR *lastc = NULL; for(i = 0; i < 12; i++){ menu_choose[i].name = NULL; KS_OSDATASET(&menu_choose[i], KS_NONE); } menu_choose[0].name = "^G"; menu_choose[0].label = N_("Get Help"); KS_OSDATASET(&menu_choose[0], KS_SCREENHELP); menu_choose[6].name = "^C"; menu_choose[6].label = N_("Cancel"); KS_OSDATASET(&menu_choose[6], KS_NONE); for(i = 0; i < 10; i++){ if((i % 2) == 0){ menu_choose[i / 2 + 1].name = extras[i].name; menu_choose[i / 2 + 1].label = extras[i].label; } else{ menu_choose[(i + 13) / 2].name = extras[i].name; menu_choose[(i + 13) / 2].label = extras[i].label; } } wkeyhelp(menu_choose); /* paint generic menu */ sgarbk = TRUE; /* mark menu dirty */ if(Pmaster && curwp) curwp->w_flag |= WFMODE; ucs4_strncpy(buf, prompt, NLINE); buf[NLINE-1] = '\0'; mlwrite(buf, NULL); if(Pmaster && Pmaster->colors && Pmaster->colors->prcp && pico_is_good_colorpair(Pmaster->colors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); } else (*term.t_rev)(1); return_val = -1; while(1){ c = GetKey(); for(i = 0; i < 10 && extras[i].name != NULL && extras[i].key != c; i++) ; if(i < 10 && extras[i].name) return_val = c; else switch(c){ case (CTRL|'C') : /* Bail out! */ case F2 : pputs_utf8(_("Cancel"), 1); return_val = -2; break; case (CTRL|'G') : if(term.t_mrow == 0 && km_popped == 0){ movecursor(term.t_nrow-2, 0); peeol(); term.t_mrow = 2; if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); wkeyhelp(menu_choose); /* paint generic menu */ mlwrite(buf, NULL); if(Pmaster && Pmaster->colors && Pmaster->colors->prcp && pico_is_good_colorpair(Pmaster->colors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(Pmaster->colors->prcp, PSC_NONE); } else (*term.t_rev)(1); sgarbk = TRUE; /* mark menu dirty */ km_popped++; break; } /* else fall through */ default: (*term.t_beep)(); case NODATA : break; } (*term.t_flush)(); if (return_val != -1){ /* abort sets rv = -2, other return values are positive */ if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); if(km_popped){ term.t_mrow = 0; movecursor(term.t_nrow, 0); peeol(); sgarbf = 1; km_popped = 0; } return(return_val); } } } int mlyesno_utf8(char *utf8prompt, int dflt) { int ret; UCS *prompt; prompt = utf8_to_ucs4_cpystr(utf8prompt ? utf8prompt : ""); ret = mlyesno(prompt, dflt); if(prompt) fs_give((void **) &prompt); return(ret); } /* * Ask a yes or no question in the message line. Return either TRUE, FALSE, or * ABORT. The ABORT status is returned if the user bumps out of the question * with a ^G. if d >= 0, d is the default answer returned. Otherwise there * is no default. */ int mlyesno(UCS *prompt, int dflt) { int rv; UCS buf[NLINE], lbuf[10]; KEYMENU menu_yesno[12]; COLOR_PAIR *lastc = NULL; PCOLORS *pcolors = Pmaster && Pmaster->colors ? Pmaster->colors : Pcolors; #ifdef _WINDOWS if (mswin_usedialog ()) switch (mswin_yesno (prompt)) { default: case 0: return (ABORT); case 1: return (TRUE); case 2: return (FALSE); } #endif for(rv = 0; rv < 12; rv++){ menu_yesno[rv].name = NULL; KS_OSDATASET(&menu_yesno[rv], KS_NONE); } menu_yesno[1].name = "Y"; menu_yesno[1].label = (dflt == TRUE) ? "[" N_("Yes") "]" : N_("Yes"); menu_yesno[6].name = "^C"; menu_yesno[6].label = N_("Cancel"); menu_yesno[7].name = "N"; menu_yesno[7].label = (dflt == FALSE) ? "[" N_("No") "]" : N_("No"); wkeyhelp(menu_yesno); /* paint generic menu */ sgarbk = TRUE; /* mark menu dirty */ if(Pmaster && curwp) curwp->w_flag |= WFMODE; ucs4_strncpy(buf, prompt, NLINE); buf[NLINE-1] = '\0'; lbuf[0] = ' '; lbuf[1] = '?'; lbuf[2] = ' '; lbuf[3] = '\0'; ucs4_strncat(buf, lbuf, NLINE - ucs4_strlen(buf) - 1); buf[NLINE-1] = '\0'; mlwrite(buf, NULL); if(pcolors && pcolors->prcp && pico_is_good_colorpair(pcolors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(pcolors->prcp, PSC_NONE); } else (*term.t_rev)(1); rv = -1; while(1){ switch(GetKey()){ case (CTRL|'M') : /* default */ if(dflt >= 0){ pputs_utf8((dflt) ? _("Yes") : _("No"), 1); rv = dflt; } else (*term.t_beep)(); break; case (CTRL|'C') : /* Bail out! */ case F2 : pputs_utf8(_("ABORT"), 1); rv = ABORT; break; case 'y' : case 'Y' : case F3 : pputs_utf8(_("Yes"), 1); rv = TRUE; break; case 'n' : case 'N' : case F4 : pputs_utf8(_("No"), 1); rv = FALSE; break; case (CTRL|'G') : if(term.t_mrow == 0 && km_popped == 0){ movecursor(term.t_nrow-2, 0); peeol(); term.t_mrow = 2; if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); wkeyhelp(menu_yesno); /* paint generic menu */ mlwrite(buf, NULL); if(pcolors && pcolors->prcp && pico_is_good_colorpair(pcolors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(pcolors->prcp, PSC_NONE); } else (*term.t_rev)(1); sgarbk = TRUE; /* mark menu dirty */ km_popped++; break; } /* else fall through */ default: (*term.t_beep)(); case NODATA : break; } (*term.t_flush)(); if(rv != -1){ if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); if(km_popped){ term.t_mrow = 0; movecursor(term.t_nrow, 0); peeol(); sgarbf = 1; km_popped = 0; } return(rv); } } } /* * Write a prompt into the message line, then read back a response. Keep * track of the physical position of the cursor. If we are in a keyboard * macro throw the prompt away, and return the remembered response. This * lets macros run at full speed. The reply is always terminated by a carriage * return. Handle erase, kill, and abort keys. */ int mlreply_utf8(char *utf8prompt, char *utf8buf, int nbuf, int flg, EXTRAKEYS *extras) { return(mlreplyd_utf8(utf8prompt, utf8buf, nbuf, flg|QDEFLT, extras)); } int mlreply(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) { return(mlreplyd(prompt, buf, nbuf, flg|QDEFLT, extras)); } /* * function key mappings */ static UCS rfkm[12][2] = { { F1, (CTRL|'G')}, { F2, (CTRL|'C')}, { F3, 0 }, { F4, 0 }, { F5, 0 }, { F6, 0 }, { F7, 0 }, { F8, 0 }, { F9, 0 }, { F10, 0 }, { F11, 0 }, { F12, 0 } }; int mlreplyd_utf8(char *utf8prompt, char *utf8buf, int nbuf, int flg, EXTRAKEYS *extras) { int ret; UCS *b, *buf; char *utf8; UCS *prompt; buf = (UCS *) fs_get(nbuf * sizeof(*b)); b = utf8_to_ucs4_cpystr(utf8buf); if(b){ ucs4_strncpy(buf, b, nbuf); buf[nbuf-1] = '\0'; fs_give((void **) &b); } prompt = utf8_to_ucs4_cpystr(utf8prompt ? utf8prompt : ""); ret = mlreplyd(prompt, buf, nbuf, flg, extras); utf8 = ucs4_to_utf8_cpystr(buf); if(utf8){ strncpy(utf8buf, utf8, nbuf); utf8buf[nbuf-1] = '\0'; fs_give((void **) &utf8); } if(buf) fs_give((void **) &buf); if(prompt) fs_give((void **) &prompt); return(ret); } void writeachar(UCS ucs) { pputc(ucs, 0); } /* * mlreplyd - write the prompt to the message line along with a default * answer already typed in. Carriage return accepts the * default. answer returned in buf which also holds the initial * default, nbuf is its length, def set means use default value. * In order to be able to eliminate keys from a menu, EXTRAKEYS * always has size 10. */ int mlreplyd(UCS *prompt, UCS *buf, int nbuf, int flg, EXTRAKEYS *extras) { UCS c; /* current char */ UCS *b; /* pointer in buf */ int i, j; int plen; int changed = FALSE; int return_val = 0; KEYMENU menu_mlreply[12]; UCS extra_v[12]; struct display_line dline; COLOR_PAIR *lastc = NULL; PCOLORS *pcolors = Pmaster && Pmaster->colors ? Pmaster->colors : Pcolors; #ifdef _WINDOWS if(mswin_usedialog()){ MDlgButton btn_list[12]; LPTSTR free_names[12]; LPTSTR free_labels[12]; int i, j; memset(&free_names, 0, sizeof(LPTSTR) * 12); memset(&free_labels, 0, sizeof(LPTSTR) * 12); memset(&btn_list, 0, sizeof (MDlgButton) * 12); j = 0; for(i = 0; extras && extras[i].name != NULL; ++i) { if(extras[i].label[0] != '\0') { if((extras[i].key & CTRL) == CTRL) btn_list[j].ch = (extras[i].key & ~CTRL) - '@'; else btn_list[j].ch = extras[i].key; btn_list[j].rval = extras[i].key; free_names[j] = utf8_to_lptstr(extras[i].name); btn_list[j].name = free_names[j]; free_labels[j] = utf8_to_lptstr(extras[i].label); btn_list[j].label = free_labels[j]; j++; } } btn_list[j].ch = -1; return_val = mswin_dialog(prompt, buf, nbuf, ((flg&QDEFLT) > 0), FALSE, btn_list, NULL, 0); if(return_val == 3) return_val = HELPCH; for(i = 0; i < 12; i++){ if(free_names[i]) fs_give((void **) &free_names[i]); if(free_labels[i]) fs_give((void **) &free_labels[i]); } return(return_val); } #endif memset(&menu_mlreply, 0, 12*sizeof(KEYMENU)); menu_mlreply[0].name = "^G"; menu_mlreply[0].label = N_("Get Help"); KS_OSDATASET(&menu_mlreply[0], KS_SCREENHELP); for(j = 0, i = 1; i < 6; i++){ /* insert odd extras */ menu_mlreply[i].name = NULL; KS_OSDATASET(&menu_mlreply[i], KS_NONE); rfkm[2*i][1] = 0; if(extras){ j = 2*(i-1); if(extras[j].name){ rfkm[2*i][1] = extras[j].key; menu_mlreply[i].name = extras[j].name; menu_mlreply[i].label = extras[j].label; KS_OSDATASET(&menu_mlreply[i], KS_OSDATAGET(&extras[j])); } } } menu_mlreply[6].name = "^C"; menu_mlreply[6].label = N_("Cancel"); KS_OSDATASET(&menu_mlreply[6], KS_NONE); for(j = 0, i = 7; i < 12; i++){ /* insert even extras */ menu_mlreply[i].name = NULL; rfkm[2*(i-6)+1][1] = 0; if(extras){ j = 2*(i-6) - 1; if(extras[j].name){ rfkm[2*(i-6)+1][1] = extras[j].key; menu_mlreply[i].name = extras[j].name; menu_mlreply[i].label = extras[j].label; KS_OSDATASET(&menu_mlreply[i], KS_OSDATAGET(&extras[j])); } } } /* set up what to watch for and return values */ memset(extra_v, 0, sizeof(extra_v)); for(i = 0, j = 0; i < 12 && extras && extras[i].name; i++) extra_v[j++] = extras[i].key; plen = mlwrite(prompt, NULL); /* paint prompt */ if(!(flg&QDEFLT)) *buf = '\0'; dline.vused = ucs4_strlen(buf); dline.dwid = term.t_ncol - plen; dline.row = term.t_nrow - term.t_mrow; dline.col = plen; dline.dlen = 2 * dline.dwid + 100; dline.dl = (UCS *) fs_get(dline.dlen * sizeof(UCS)); dline.olddl = (UCS *) fs_get(dline.dlen * sizeof(UCS)); memset(dline.dl, 0, dline.dlen * sizeof(UCS)); memset(dline.olddl, 0, dline.dlen * sizeof(UCS)); dline.movecursor = movecursor; dline.writechar = writeachar; dline.vl = buf; dline.vlen = nbuf-1; dline.vbase = 0; b = &buf[(flg & QBOBUF) ? 0 : ucs4_strlen(buf)]; wkeyhelp(menu_mlreply); /* paint generic menu */ sgarbk = 1; /* mark menu dirty */ if(pcolors && pcolors->prcp && pico_is_good_colorpair(pcolors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(pcolors->prcp, PSC_NONE); } else (*term.t_rev)(1); for(;;){ line_paint(b-buf, &dline, NULL); (*term.t_flush)(); #ifdef MOUSE mouse_in_content(KEY_MOUSE, -1, -1, 0x5, 0); register_mfunc(mouse_in_content, term.t_nrow - term.t_mrow, plen, term.t_nrow - term.t_mrow, term.t_ncol-1); #endif #ifdef _WINDOWS mswin_allowpaste(MSWIN_PASTE_LINE); #endif while((c = GetKey()) == NODATA) ; #ifdef MOUSE clear_mfunc(mouse_in_content); #endif #ifdef _WINDOWS mswin_allowpaste(MSWIN_PASTE_DISABLE); #endif switch(c = normalize_cmd(c, rfkm, 1)){ case (CTRL|'A') : /* CTRL-A beginning */ case KEY_HOME : b = buf; continue; case (CTRL|'B') : /* CTRL-B back a char */ case KEY_LEFT: if(b <= buf) (*term.t_beep)(); else b--; continue; case (CTRL|'C') : /* CTRL-C abort */ pputs_utf8(_("ABORT"), 1); ctrlg(FALSE, 0); return_val = ABORT; goto ret; case (CTRL|'E') : /* CTRL-E end of line */ case KEY_END : b = &buf[ucs4_strlen(buf)]; continue; case (CTRL|'F') : /* CTRL-F forward a char*/ case KEY_RIGHT : if(*b == '\0') (*term.t_beep)(); else b++; continue; case (CTRL|'G') : /* CTRL-G help */ if(term.t_mrow == 0 && km_popped == 0){ movecursor(term.t_nrow-2, 0); peeol(); sgarbk = 1; /* mark menu dirty */ km_popped++; term.t_mrow = 2; if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); wkeyhelp(menu_mlreply); /* paint generic menu */ plen = mlwrite(prompt, NULL); /* paint prompt */ if(pcolors && pcolors->prcp && pico_is_good_colorpair(pcolors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(pcolors->prcp, PSC_NONE); } else (*term.t_rev)(1); pputs(buf, 1); break; } pputs_utf8(_("HELP"), 1); return_val = HELPCH; goto ret; case (CTRL|'H') : /* CTRL-H backspace */ case 0x7f : /* rubout */ if (b <= buf){ (*term.t_beep)(); break; } else b--; case (CTRL|'D') : /* CTRL-D delete char */ case KEY_DEL : if (!*b){ (*term.t_beep)(); break; } changed=TRUE; i = 0; dline.vused--; do /* blat out left char */ b[i] = b[i+1]; while(b[i++] != '\0'); break; case (CTRL|'L') : /* CTRL-L redraw */ return_val = (CTRL|'L'); goto ret; case (CTRL|'K') : /* CTRL-K kill line */ changed=TRUE; buf[0] = '\0'; dline.vused = 0; b = buf; break; case F1 : /* sort of same thing */ return_val = HELPCH; goto ret; case (CTRL|'M') : /* newline */ return_val = changed; goto ret; #ifdef MOUSE case KEY_MOUSE : { MOUSEPRESS mp; mouse_get_last (NULL, &mp); /* The clicked line have anything special on it? */ switch(mp.button){ case M_BUTTON_LEFT : /* position cursor */ mp.col -= plen; /* normalize column */ if(mp.col >= 0 && mp.col <= ucs4_strlen(buf)) b = buf + mp.col; break; case M_BUTTON_RIGHT : #ifdef _WINDOWS mswin_allowpaste(MSWIN_PASTE_LINE); mswin_paste_popup(); mswin_allowpaste(MSWIN_PASTE_DISABLE); break; #endif case M_BUTTON_MIDDLE : /* NO-OP for now */ default: /* just ignore */ break; } } continue; #endif default : /* look for match in extra_v */ for(i = 0; i < 12; i++) if(c && c == extra_v[i]){ return_val = c; goto ret; } changed=TRUE; if(c & (CTRL | FUNC)){ /* bag ctrl_special chars */ (*term.t_beep)(); } else{ i = ucs4_strlen(b); if(flg&QNODQT){ /* reject double quotes? */ if(c == '"'){ (*term.t_beep)(); continue; } } if(dline.vused >= nbuf-1){ (*term.t_beep)(); continue; } do /* blat out left char */ b[i+1] = b[i]; while(i-- > 0); dline.vused++; *b++ = c; } } } ret: if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); (*term.t_flush)(); if(km_popped){ term.t_mrow = 0; movecursor(term.t_nrow, 0); peeol(); sgarbf = 1; km_popped = 0; } if(dline.dl) fs_give((void **) &dline.dl); if(dline.olddl) fs_give((void **) &dline.olddl); return(return_val); } void emlwwrite(char *utf8message, EML *eml) { (*term.t_beep)(); emlwrite(utf8message, eml); } void emlwrite(char *utf8message, EML *eml) { UCS *message; message = utf8_to_ucs4_cpystr(utf8message ? utf8message : ""); emlwrite_ucs4(message, eml); if(message) fs_give((void **) &message); } /* * emlwrite() - write the message string to the error half of the screen * center justified. much like mlwrite (which is still used * to paint the line for prompts and such), except it center * the text. */ void emlwrite_ucs4(UCS *message, EML *eml) { UCS *bufp, *ap; int width; COLOR_PAIR *lastc = NULL; PCOLORS *pcolors = Pmaster && Pmaster->colors ? Pmaster->colors : Pcolors; mlerase(); if(!(message && *message) || term.t_nrow < 2) return; /* nothing to write or no space to write, bag it */ bufp = message; width = ucs4_str_width(message); /* * next, figure out where the to move the cursor so the message * comes out centered */ if((ap=ucs4_strchr(message, '%')) != NULL){ width -= 2; switch(ap[1]){ case '%': case 'c': width += (eml && eml->c) ? wcellwidth(eml->c) : 1; break; case 'd': width += dumbroot(eml ? eml->d : 0, 10); break; case 'D': width += dumblroot(eml ? eml->l : 0L, 10); break; case 'o': width += dumbroot(eml ? eml->d : 0, 8); break; case 'x': width += dumbroot(eml ? eml->d : 0, 16); break; case 's': /* string arg is UTF-8 */ width += (eml && eml->s) ? utf8_width(eml->s) : 2; break; } } if(width+4 <= term.t_ncol) movecursor(term.t_nrow-term.t_mrow, (term.t_ncol - (width + 4))/2); else movecursor(term.t_nrow-term.t_mrow, 0); if(pcolors && pcolors->stcp && pico_is_good_colorpair(pcolors->stcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(pcolors->stcp, PSC_NONE); } else (*term.t_rev)(1); pputs_utf8("[ ", 1); while (*bufp != '\0' && ttcol < term.t_ncol-2){ if(*bufp == '\007') (*term.t_beep)(); else if(*bufp == '%'){ switch(*++bufp){ case 'c': if(eml && eml->c) pputc(eml->c, 0); else { pputs_utf8("%c", 0); } break; case 'd': mlputi(eml ? eml->d : 0, 10); break; case 'D': mlputli(eml ? eml->l : 0L, 10); break; case 'o': mlputi(eml ? eml->d : 0, 16); break; case 'x': mlputi(eml ? eml->d : 0, 8); break; case 's': pputs_utf8((eml && eml->s) ? eml->s : "%s", 0); break; case '%': default: pputc(*bufp, 0); break; } } else pputc(*bufp, 0); bufp++; } pputs_utf8(" ]", 1); if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); (*term.t_flush)(); mpresf = TRUE; } int mlwrite_utf8(char *utf8fmt, void *arg) { UCS *fmt; int ret; fmt = utf8_to_ucs4_cpystr(utf8fmt ? utf8fmt : ""); ret = mlwrite(fmt, arg); if(fmt) fs_give((void **) &fmt); return(ret); } /* * Write a message into the message line. Keep track of the physical cursor * position. A small class of printf like format items is handled. Assumes the * stack grows down; this assumption is made by the "++" in the argument scan * loop. Set the "message line" flag TRUE. */ int mlwrite(UCS *fmt, void *arg) { int ret, ww; UCS c; char *ap; COLOR_PAIR *lastc = NULL; PCOLORS *pcolors = Pmaster && Pmaster->colors ? Pmaster->colors : Pcolors; /* * the idea is to only highlight if there is something to show */ mlerase(); movecursor(ttrow, 0); if(pcolors && pcolors->prcp && pico_is_good_colorpair(pcolors->prcp)){ lastc = pico_get_cur_color(); (void) pico_set_colorp(pcolors->prcp, PSC_NONE); } else (*term.t_rev)(1); ap = (char *) &arg; while ((c = *fmt++) != 0) { if (c != '%') { pputc(c, 1); } else { c = *fmt++; switch (c){ case 'd': mlputi(*(int *)ap, 10); ap += sizeof(int); break; case 'o': mlputi(*(int *)ap, 8); ap += sizeof(int); break; case 'x': mlputi(*(int *)ap, 16); ap += sizeof(int); break; case 'D': mlputli(*(long *)ap, 10); ap += sizeof(long); break; case 's': pputs_utf8(*(char **)ap, 1); ap += sizeof(char *); break; default: pputc(c, 1); ww = wcellwidth(c); ttcol += (ww >= 0 ? ww : 1); } } } ret = ttcol; while(ttcol < term.t_ncol) pputc(' ', 0); movecursor(term.t_nrow - term.t_mrow, ret); if(lastc){ (void) pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); (*term.t_flush)(); mpresf = TRUE; return(ret); } /* * Write out an integer, in the specified radix. Update the physical cursor * position. This will not handle any negative numbers; maybe it should. */ void mlputi(int i, int r) { register int q; static char hexdigits[] = "0123456789ABCDEF"; if (i < 0){ i = -i; pputc('-', 1); } q = i/r; if (q != 0) mlputi(q, r); pputc(hexdigits[i%r], 1); } /* * do the same except as a long integer. */ void mlputli(long l, int r) { register long q; if (l < 0){ l = -l; pputc('-', 1); } q = l/r; if (q != 0) mlputli(q, r); pputc((int)(l%r)+'0', 1); } void unknown_command(UCS c) { char buf[10], ch, *s; EML eml; buf[0] = '\0'; s = buf; if(!c){ /* fall through */ } else if(c & CTRL && c >= (CTRL|'@') && c <= (CTRL|'_')){ ch = c - (CTRL|'@') + '@'; snprintf(s, sizeof(buf), "^%c", ch); } else switch(c){ case ' ' : s = "SPACE"; break; case '\033' : s = "ESC"; break; case '\177' : s = "DEL"; break; case ctrl('I') : s = "TAB"; break; case ctrl('J') : s = "LINEFEED"; break; case ctrl('M') : s = "RETURN"; break; case ctrl('Q') : s = "XON"; break; case ctrl('S') : s = "XOFF"; break; case KEY_UP : s = "Up Arrow"; break; case KEY_DOWN : s = "Down Arrow"; break; case KEY_RIGHT : s = "Right Arrow"; break; case KEY_LEFT : s = "Left Arrow"; break; case CTRL|KEY_UP : s = "Ctrl-Up Arrow"; break; case CTRL|KEY_DOWN : s = "Ctrl-Down Arrow"; break; case CTRL|KEY_RIGHT : s = "Ctrl-Right Arrow"; break; case CTRL|KEY_LEFT : s = "Ctrl-Left Arrow"; break; case KEY_PGUP : s = "Prev Page"; break; case KEY_PGDN : s = "Next Page"; break; case KEY_HOME : s = "Home"; break; case KEY_END : s = "End"; break; case KEY_DEL : s = "Delete"; break; /* Not necessary DEL! */ case F1 : case F2 : case F3 : case F4 : case F5 : case F6 : case F7 : case F8 : case F9 : case F10 : case F11 : case F12 : snprintf(s, sizeof(buf), "F%ld", (long) (c - PF1 + 1)); break; default: if(c < CTRL) utf8_put((unsigned char *) s, (unsigned long) c); break; } eml.s = s; emlwrite("Unknown Command: %s", &eml); (*term.t_beep)(); } /* * scrolldown - use stuff to efficiently move blocks of text on the * display, and update the pscreen array to reflect those * moves... * * wp is the window to move in * r is the row at which to begin scrolling * n is the number of lines to scrol */ void scrolldown(WINDOW *wp, int r, int n) { #ifdef TERMCAP register int i; register int l; register VIDEO *vp1; register VIDEO *vp2; if(!n) return; if(r < 0){ r = wp->w_toprow; l = wp->w_ntrows; } else{ if(r > wp->w_toprow) vscreen[r-1]->v_flag |= VFCHG; l = wp->w_toprow+wp->w_ntrows-r; } o_scrolldown(r, n); for(i=l-n-1; i >= 0; i--){ vp1 = pscreen[r+i]; vp2 = pscreen[r+i+n]; memcpy(vp2, vp1, term.t_ncol * sizeof(CELL)); } pprints(r+n-1, r); ttrow = FARAWAY; ttcol = FARAWAY; #endif /* TERMCAP */ } /* * scrollup - use tcap stuff to efficiently move blocks of text on the * display, and update the pscreen array to reflect those * moves... */ void scrollup(WINDOW *wp, int r, int n) { #ifdef TERMCAP register int i; register VIDEO *vp1; register VIDEO *vp2; if(!n) return; if(r < 0) r = wp->w_toprow; o_scrollup(r, n); i = 0; while(1){ if(Pmaster){ if(!(r+i+n < wp->w_toprow+wp->w_ntrows)) break; } else{ if(!((i < wp->w_ntrows-n)&&(r+i+n < wp->w_toprow+wp->w_ntrows))) break; } vp1 = pscreen[r+i+n]; vp2 = pscreen[r+i]; memcpy(vp2, vp1, term.t_ncol * sizeof(CELL)); i++; } pprints(wp->w_toprow+wp->w_ntrows-n, wp->w_toprow+wp->w_ntrows-1); ttrow = FARAWAY; ttcol = FARAWAY; #endif /* TERMCAP */ } /* * print spaces in the physical screen starting from row abs(n) working in * either the positive or negative direction (depending on sign of n). */ void pprints(int x, int y) { register int i; register int j; if(x < y){ for(i = x;i <= y; ++i){ for(j = 0; j < term.t_ncol; j++){ pscreen[i]->v_text[j].c = ' '; pscreen[i]->v_text[j].a = 0; } } } else{ for(i = x;i >= y; --i){ for(j = 0; j < term.t_ncol; j++){ pscreen[i]->v_text[j].c = ' '; pscreen[i]->v_text[j].a = 0; } } } ttrow = y; ttcol = 0; } /* * doton - return the physical line number that the dot is on in the * current window, and by side effect the number of lines remaining */ int doton(int *r, unsigned *chs) { register int i = 0; register LINE *lp = curwp->w_linep; int l = -1; assert(r != NULL && chs != NULL); *chs = 0; while(i++ < curwp->w_ntrows){ if(lp == curwp->w_dotp) l = i-1; lp = lforw(lp); if(lp == curwp->w_bufp->b_linep){ i++; break; } if(l >= 0) (*chs) += llength(lp); } *r = i - l - term.t_mrow; return(l+curwp->w_toprow); } /* * resize_pico - given new window dimensions, allocate new resources */ int resize_pico(int row, int col) { int old_nrow, old_ncol; register int i; register VIDEO *vp; old_nrow = term.t_nrow; old_ncol = term.t_ncol; term.t_nrow = row; term.t_ncol = col; if (old_ncol == term.t_ncol && old_nrow == term.t_nrow) return(TRUE); if(curwp){ curwp->w_toprow = 2; curwp->w_ntrows = term.t_nrow - curwp->w_toprow - term.t_mrow; } if(Pmaster){ fillcol = Pmaster->fillcolumn; (*Pmaster->resize)(); } else if(userfillcol > 0) fillcol = userfillcol; else fillcol = term.t_ncol - 6; /* we control the fill column */ /* * free unused screen space ... */ for(i=term.t_nrow+1; i <= old_nrow; ++i){ free((char *) vscreen[i]); free((char *) pscreen[i]); } /* * realloc new space for screen ... */ if((vscreen=(VIDEO **)realloc(vscreen,(term.t_nrow+1)*sizeof(VIDEO *))) == NULL){ if(Pmaster) return(-1); else exit(1); } if((pscreen=(VIDEO **)realloc(pscreen,(term.t_nrow+1)*sizeof(VIDEO *))) == NULL){ if(Pmaster) return(-1); else exit(1); } for (i = 0; i <= term.t_nrow; ++i) { if(i <= old_nrow) vp = (VIDEO *) realloc(vscreen[i], sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); else vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); if (vp == NULL) exit(1); vp->v_flag = VFCHG; vscreen[i] = vp; if(old_ncol < term.t_ncol){ /* don't let any garbage in */ vtrow = i; vtcol = (i < old_nrow) ? old_ncol : 0; vteeol(); } if(i <= old_nrow) vp = (VIDEO *) realloc(pscreen[i], sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); else vp = (VIDEO *) malloc(sizeof(VIDEO)+(term.t_ncol*sizeof(CELL))); if (vp == NULL) exit(1); vp->v_flag = VFCHG; pscreen[i] = vp; } if(!ResizeBrowser()){ if(Pmaster && Pmaster->headents){ ResizeHeader(); } else{ curwp->w_flag |= (WFHARD | WFMODE); pico_refresh(0, 1); /* redraw whole enchilada. */ update(); /* do it */ } } return(TRUE); } void redraw_pico_for_callback(void) { pico_refresh(0, 1); update(); } /* * showCompTitle - display the anchor line passed in from pine */ void showCompTitle(void) { if(Pmaster){ UCS *bufp; extern UCS *pico_anchor; COLOR_PAIR *lastc = NULL; if((bufp = pico_anchor) == NULL) return; movecursor(COMPOSER_TITLE_LINE, 0); if (Pmaster->colors && Pmaster->colors->tbcp && pico_is_good_colorpair(Pmaster->colors->tbcp)){ lastc = pico_get_cur_color(); (void)pico_set_colorp(Pmaster->colors->tbcp, PSC_NONE); } else (*term.t_rev)(1); while (ttcol < term.t_ncol) if(*bufp != '\0') pputc(*bufp++, 1); else pputc(' ', 1); if (lastc){ (void)pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } else (*term.t_rev)(0); movecursor(COMPOSER_TITLE_LINE + 1, 0); peeol(); } } /* * zotdisplay - blast malloc'd space created for display maps */ void zotdisplay(void) { register int i; for (i = 0; i <= term.t_nrow; ++i){ /* free screens */ free((char *) vscreen[i]); free((char *) pscreen[i]); } free((char *) vscreen); free((char *) pscreen); } /* * nlforw() - returns the number of lines from the top to the dot */ int nlforw(void) { register int i = 0; register LINE *lp = curwp->w_linep; while(lp != curwp->w_dotp){ lp = lforw(lp); i++; } return(i); } /* * pputc - output the given char, keep track of it on the physical screen * array, and keep track of the cursor */ void pputc(UCS c, /* char to write */ int a) /* and its attribute */ { int ind, width, printable_ascii = 0; /* * This is necessary but not sufficient to allow us to draw. Note that * ttrow runs from 0 to t_nrow (so total number of rows is t_nrow+1) * ttcol runs from 0 to t_ncol-1 (so total number of cols is t_ncol) */ if((ttcol >= 0 && ttcol < term.t_ncol) && (ttrow >= 0 && ttrow <= term.t_nrow)){ /* * Width is the number of screen columns a character will occupy. */ if(c < 0x80 && isprint(c)){ printable_ascii++; width = 1; } else width = wcellwidth(c); if(width < 0) width = 1; /* will be a '?' */ if(ttcol + width <= term.t_ncol){ /* it fits */ /* * Some terminals scroll when you write in the lower right corner * of the screen, so don't write there. */ if(!(ttrow == term.t_nrow && ttcol+width == term.t_ncol)){ (*term.t_putchar)(c); /* write it */ ind = index_from_col(ttrow, ttcol); pscreen[ttrow]->v_text[ind].c = c; /* keep track of it */ pscreen[ttrow]->v_text[ind].a = a; /* keep track of it */ } } else{ /* * Character overlaps right edge of screen. Hopefully the higher * layers will prevent this but we're making sure. * * We may want to do something like writing a space character * into the cells that are on the screen. We'll see. */ } ttcol = MIN(term.t_ncol, ttcol+width); } } /* * pputs - print a string and keep track of the cursor */ void pputs(UCS *s, /* string to write */ int a) /* and its attribute */ { while (*s != '\0') pputc(*s++, a); } void pputs_utf8(char *s, int a) { UCS *ucsstr = NULL; if(s && *s){ ucsstr = utf8_to_ucs4_cpystr(s); if(ucsstr){ pputs(ucsstr, a); fs_give((void **) &ucsstr); } } } /* * peeol - physical screen array erase to end of the line. remember to * track the cursor. */ void peeol(void) { int i, width = 0, ww; CELL cl; if(ttrow < 0 || ttrow > term.t_nrow) return; cl.c = ' '; cl.a = 0; /* * Don't clear if we think we are sitting past the last column, * that erases the last column if we just wrote it. */ if(ttcol < term.t_ncol) (*term.t_eeol)(); /* * Because the characters are variable width it's a little tricky * to erase the rest of the line. What we do is add up the * widths of the characters until we reach ttcol * then set the rest to the space character. */ for(i = 0; i < term.t_ncol && width < ttcol; i++){ ww = wcellwidth((UCS) pscreen[ttrow]->v_text[i].c); width += (ww >= 0 ? ww : 1); } while(i < term.t_ncol) pscreen[ttrow]->v_text[i++] = cl; } /* * pscr - return the character cell on the physical screen map on the * given line, l, and offset, o. */ CELL * pscr(int l, int o) { if((l >= 0 && l <= term.t_nrow) && (o >= 0 && o < term.t_ncol)) return(&(pscreen[l]->v_text[o])); else return(NULL); } /* * pclear() - clear the physical screen from row x through row y (inclusive) * row is zero origin, min row = 0 max row = t_nrow * Clear whole screen -- pclear(0, term.t_nrow) * Clear bottom two rows -- pclear(term.t_nrow-1, term.t_nrow) * Clear bottom three rows -- pclear(term.t_nrow-2, term.t_nrow) */ void pclear(int x, int y) { register int i; x = MIN(MAX(0, x), term.t_nrow); y = MIN(MAX(0, y), term.t_nrow); for(i=x; i <= y; i++){ movecursor(i, 0); peeol(); } } /* * dumbroot - just get close */ int dumbroot(int x, int b) { if(x < b) return(1); else return(dumbroot(x/b, b) + 1); } /* * dumblroot - just get close */ int dumblroot(long x, int b) { if(x < b) return(1); else return(dumblroot(x/b, b) + 1); } /* * pinsertc - use optimized insert, fixing physical screen map. * returns true if char written, false otherwise */ int pinsert(CELL c) { int i, ind = 0, ww; CELL *p; if(ttrow < 0 || ttrow > term.t_nrow) return(0); if(o_insert((UCS) c.c)){ /* if we've got it, use it! */ p = pscreen[ttrow]->v_text; /* then clean up physical screen */ ind = index_from_col(ttrow, ttcol); for(i = term.t_ncol-1; i > ind; i--) p[i] = p[i-1]; /* shift right */ p[ind] = c; /* insert new char */ ww = wcellwidth((UCS) c.c); ttcol += (ww >= 0 ? ww : 1); return(1); } return(0); } /* * pdel - use optimized delete to rub out the current char and * fix the physical screen array. * returns true if optimized the delete, false otherwise */ int pdel(void) { int i, ind = 0, w; CELL *p; if(ttrow < 0 || ttrow > term.t_nrow) return(0); if(TERM_DELCHAR){ /* if we've got it, use it! */ p = pscreen[ttrow]->v_text; ind = index_from_col(ttrow, ttcol); if(ind > 0){ --ind; w = wcellwidth((UCS) p[ind].c); w = (w >= 0 ? w : 1); ttcol -= w; for(i = 0; i < w; i++){ (*term.t_putchar)('\b'); /* move left a char */ o_delete(); /* and delete it */ } /* then clean up physical screen */ for(i=ind; i < term.t_ncol-1; i++) p[i] = p[i+1]; p[i].c = ' '; p[i].a = 0; } return(1); } return(0); } /* * wstripe - write out the given string at the given location, and reverse * video on flagged characters. Does the same thing as pine's * stripe. * * I believe this needs to be fixed to work with non-ascii utf8pmt, but maybe * only if you want to put the tildes before multi-byte chars. */ void wstripe(int line, int column, char *utf8pmt, int key) { UCS *ucs4pmt, *u; int i = 0, col = 0; int j = 0; int l, ww; COLOR_PAIR *lastc = NULL; COLOR_PAIR *kncp = NULL; COLOR_PAIR *klcp = NULL; if(line < 0 || line > term.t_nrow) return; if (Pmaster && Pmaster->colors){ if(pico_is_good_colorpair(Pmaster->colors->klcp)) klcp = Pmaster->colors->klcp; if(klcp && pico_is_good_colorpair(Pmaster->colors->kncp)) kncp = Pmaster->colors->kncp; } else if(Pcolors){ klcp = Pcolors->klcp; kncp = Pcolors->kncp; } lastc = pico_get_cur_color(); ucs4pmt = utf8_to_ucs4_cpystr(utf8pmt); l = ucs4_strlen(ucs4pmt); while(1){ if(i >= term.t_ncol || col >= term.t_ncol || j >= l){ if(lastc) free_color_pair(&lastc); if(ucs4pmt) fs_give((void **) &ucs4pmt); return; /* equal strings */ } if(ucs4pmt[j] == (UCS) key) j++; if (pscr(line, i) == NULL){ if(lastc) free_color_pair(&lastc); if(ucs4pmt) fs_give((void **) &ucs4pmt); return; } if(pscr(line, i)->c != ucs4pmt[j]){ if(j >= 1 && ucs4pmt[j-1] == (UCS) key) j--; break; } ww = wcellwidth((UCS) pscr(line, i)->c); col += (ww >= 0 ? ww : 1); j++; i++; } movecursor(line, column+col); if(klcp) (void)pico_set_colorp(klcp, PSC_NONE); u = &ucs4pmt[j]; do{ if(*u == (UCS) key){ u++; if(kncp) (void)pico_set_colorp(kncp, PSC_NONE); else (void)(*term.t_rev)(1); pputc(*u, 1); if(kncp) (void)pico_set_colorp(klcp, PSC_NONE); else (void)(*term.t_rev)(0); } else{ pputc(*u, 0); } } while(*++u != '\0'); if(ucs4pmt) fs_give((void **) &ucs4pmt); peeol(); if (lastc){ (void)pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } (*term.t_flush)(); } /* * wkeyhelp - paint list of possible commands on the bottom * of the display (yet another pine clone) * NOTE: function key mode is handled here since all the labels * are the same... * * The KEYMENU definitions have names and labels defined as UTF-8 strings, * and wstripe expects UTF-8. */ void wkeyhelp(KEYMENU *keymenu) { char *obufp, *p, fkey[4]; char linebuf[2*NLINE]; /* "2" is for space for invert tokens */ int row, slot, tspace, adjusted_tspace, nspace[6], index, n; #ifdef MOUSE char nbuf[NLINE]; #endif #ifdef _WINDOWS pico_config_menu_items (keymenu); #endif if(term.t_mrow == 0) return; if(term.t_nrow < 1) return; /* * Calculate amount of space for the names column by column... */ for(index = 0; index < 6; index++) if(!(gmode&MDFKEY)){ nspace[index] = (keymenu[index].name) ? utf8_width(keymenu[index].name) : 0; if(keymenu[index+6].name && (n = utf8_width(keymenu[index+6].name)) > nspace[index]) nspace[index] = n; nspace[index]++; } else nspace[index] = (index < 4) ? 3 : 4; tspace = term.t_ncol/6; /* total space for each item */ /* * Avoid writing in bottom right corner so we won't scroll screens that * scroll when you do that. The way this is setup, we won't do that * unless the number of columns is evenly divisible by 6. */ adjusted_tspace = (6 * tspace == term.t_ncol) ? tspace - 1 : tspace; index = 0; for(row = 0; row <= 1; row++){ linebuf[0] = '\0'; obufp = &linebuf[0]; for(slot = 0; slot < 6; slot++){ if(keymenu[index].name && keymenu[index].label){ size_t l; char this_label[200], tmp_label[200]; if(keymenu[index].label[0] == '[' && keymenu[index].label[(l=strlen(keymenu[index].label))-1] == ']' && l > 2){ strncpy(tmp_label, &keymenu[index].label[1], MIN(sizeof(tmp_label),l-2)); tmp_label[MIN(sizeof(tmp_label)-1,l-2)] = '\0'; snprintf(this_label, sizeof(this_label), "[%s]", _(tmp_label)); } else strncpy(this_label, _(keymenu[index].label), sizeof(this_label)); this_label[sizeof(this_label)-1] = '\0'; if(gmode&MDFKEY){ p = fkey; snprintf(fkey, sizeof(fkey), "F%d", (2 * slot) + row + 1); } else p = keymenu[index].name; #ifdef MOUSE snprintf(nbuf, sizeof(nbuf), "%.*s %s", nspace[slot], p, this_label); register_key(index, (gmode&MDFKEY) ? F1 + (2 * slot) + row: (keymenu[index].name[0] == '^') ? (CTRL | keymenu[index].name[1]) : (keymenu[index].name[0] == 'S' && !strcmp(keymenu[index].name, "Spc")) ? ' ' : keymenu[index].name[0], nbuf, invert_label, term.t_nrow - 1 + row, (slot * tspace), strlen(nbuf), (Pmaster && Pmaster->colors) ? Pmaster->colors->kncp: NULL, (Pmaster && Pmaster->colors) ? Pmaster->colors->klcp: NULL); #endif n = nspace[slot]; while(p && *p && n--){ *obufp++ = '~'; /* insert "invert" token */ *obufp++ = *p++; } while(n-- > 0) *obufp++ = ' '; p = this_label; n = ((slot == 5 && row == 1) ? adjusted_tspace : tspace) - nspace[slot]; while(p && *p && n-- > 0) *obufp++ = *p++; while(n-- > 0) *obufp++ = ' '; } else{ n = (slot == 5 && row == 1) ? adjusted_tspace : tspace; while(n--) *obufp++ = ' '; #ifdef MOUSE register_key(index, NODATA, "", NULL, 0, 0, 0, NULL, NULL); #endif } *obufp = '\0'; index++; } wstripe(term.t_nrow - 1 + row, 0, linebuf, '~'); } } /* * This returns the screen width between pstart (inclusive) and * pend (exclusive) where the pointers point into an array of CELLs. */ unsigned cellwidth_ptr_to_ptr(CELL *pstart, CELL *pend) { CELL *p; unsigned width = 0; int ww; if(pstart) for(p = pstart; p < pend; p++){ ww = wcellwidth((UCS) p->c); width += (ww >= 0 ? ww : 1); } return(width); } /* * This returns the virtual screen width in row from index a to b (exclusive). */ unsigned vcellwidth_a_to_b(int row, int a, int b) { CELL *pstart, *pend; VIDEO *vp; if(row < 0 || row > term.t_nrow) return 0; if(a >= b) return 0; a = MIN(MAX(0, a), term.t_ncol-1); b = MIN(MAX(0, a), term.t_ncol); /* b is past where we stop */ vp = vscreen[row]; pstart = &vp->v_text[a]; pend = &vp->v_text[b]; return(cellwidth_ptr_to_ptr(pstart, pend)); } /* * This returns the physical screen width in row from index a to b (exclusive). */ unsigned pcellwidth_a_to_b(int row, int a, int b) { CELL *pstart, *pend; VIDEO *vp; if(row < 0 || row > term.t_nrow) return 0; if(a >= b) return 0; a = MIN(MAX(0, a), term.t_ncol-1); b = MIN(MAX(0, a), term.t_ncol); /* b is past where we stop */ vp = pscreen[row]; pstart = &vp->v_text[a]; pend = &vp->v_text[b]; return(cellwidth_ptr_to_ptr(pstart, pend)); } int index_from_col(int row, int col) { CELL *p_start, *p_end, *p_limit; int w_consumed = 0, w, done = 0; if(row < 0 || row > term.t_nrow) return 0; p_end = p_start = pscreen[row]->v_text; p_limit = p_start + term.t_ncol; if(p_start) while(!done && p_end < p_limit && p_end->c && w_consumed <= col){ w = wcellwidth((UCS) p_end->c); w = (w >= 0 ? w : 1); if(w_consumed + w <= col){ w_consumed += w; ++p_end; } else ++done; } /* MIN and MAX just to be sure */ return(MIN(MAX(0, p_end - p_start), term.t_ncol-1)); } #ifdef _WINDOWS void pico_config_menu_items (KEYMENU *keymenu) { int i; KEYMENU *k; UCS key; mswin_menuitemclear (); /* keymenu's seem to be hardcoded at 12 entries. */ for (i = 0, k = keymenu; i < 12; ++i, ++k) { if (k->name != NULL && k->label != NULL && k->menuitem != KS_NONE) { if (k->name[0] == '^') key = CTRL | k->name[1]; else if (strcmp(k->name, "Ret") == 0) key = '\r'; else key = k->name[0]; mswin_menuitemadd (key, k->label, k->menuitem, 0); } } } /* * Update the scroll range and position. (exported) * * This is where curbp->b_linecnt is really managed. With out this function * to count the number of lines when needed curbp->b_linecnt will never * really be correct. BUT, this function is only compiled into the * windows version, so b_linecnt will only ever be right in the windows * version. OK for now because that is the only version that * looks at b_linecnt. */ int update_scroll (void) { long scr_pos; long scr_range; LINE *lp; static LINE *last_top_line = NULL; static long last_scroll_pos = -1; if (ComposerEditing) { /* Editing header - don't allow scroll bars. */ mswin_setscrollrange (0, 0); return(0); } /* * Count the number of lines in the current buffer. Done when: * * when told to recount: curbp->b_linecnt == -1 * when the top line changed: curwp->w_linep != last_top_line * when we don't know the scroll pos: last_scroll_pos == -1 * * The first line in the list is a "place holder" line and is not * counted. The list is circular, when we return the to place * holder we have reached the end. */ if(curbp->b_linecnt == -1 || curwp->w_linep != last_top_line || last_scroll_pos == -1) { scr_range = 0; scr_pos = 0; for (lp = lforw (curbp->b_linep); lp != curbp->b_linep; lp = lforw (lp)) { if (lp == curwp->w_linep) scr_pos = scr_range; ++scr_range; } curbp->b_linecnt = scr_range; last_scroll_pos = scr_pos; last_top_line = curwp->w_linep; } /* * Set new scroll range and position. */ mswin_setscrollrange (curwp->w_ntrows - 2, curbp->b_linecnt - 1); mswin_setscrollpos (last_scroll_pos); return (0); } #endif /* _WINDOWS */