Blame textbox.c

Packit 4d380f
Packit 4d380f
#include <ctype.h>
Packit 4d380f
#include <slang.h>
Packit 4d380f
#include <stdlib.h>
Packit 4d380f
#include <string.h>
Packit 4d380f
#include <wchar.h>
Packit 4d380f
#include <wctype.h>
Packit 4d380f
Packit 4d380f
#include "newt.h"
Packit 4d380f
#include "newt_pr.h"
Packit 4d380f
Packit 4d380f
struct textbox {
Packit 4d380f
    char ** lines;
Packit 4d380f
    int numLines;
Packit 4d380f
    int linesAlloced;
Packit 4d380f
    int doWrap;
Packit 4d380f
    newtComponent sb;
Packit 4d380f
    int topLine;
Packit 4d380f
    int textWidth;
Packit 4d380f
    int isActive;
Packit 4d380f
    int cs;
Packit 4d380f
    int csActive;
Packit 4d380f
};
Packit 4d380f
Packit 4d380f
static char * expandTabs(const char * text);
Packit 4d380f
static void textboxDraw(newtComponent co);
Packit 4d380f
static void addLine(newtComponent co, const char * s, int len);
Packit 4d380f
static void doReflow(const char * text, char ** resultPtr, int width, 
Packit 4d380f
		     int * badness, int * heightPtr);
Packit 4d380f
static struct eventResult textboxEvent(newtComponent c,
Packit 4d380f
				      struct event ev);
Packit 4d380f
static void textboxDestroy(newtComponent co);
Packit 4d380f
static void textboxPlace(newtComponent co, int newLeft, int newTop);
Packit 4d380f
static void textboxMapped(newtComponent co, int isMapped);
Packit 4d380f
Packit 4d380f
static struct componentOps textboxOps = {
Packit 4d380f
    textboxDraw,
Packit 4d380f
    textboxEvent,
Packit 4d380f
    textboxDestroy,
Packit 4d380f
    textboxPlace,
Packit 4d380f
    textboxMapped,
Packit 4d380f
} ;
Packit 4d380f
Packit 4d380f
static void textboxMapped(newtComponent co, int isMapped) {
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
Packit 4d380f
    co->isMapped = isMapped;
Packit 4d380f
    if (tb->sb) {
Packit 4d380f
	tb->sb->ops->mapped(tb->sb, isMapped);
Packit 4d380f
    }
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
static void textboxPlace(newtComponent co, int newLeft, int newTop) {
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
Packit 4d380f
    co->top = newTop;
Packit 4d380f
    co->left = newLeft;
Packit 4d380f
Packit 4d380f
    if (tb->sb) {
Packit 4d380f
	tb->sb->ops->place(tb->sb, co->left + co->width - 1, co->top);
Packit 4d380f
    }
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
void newtTextboxSetHeight(newtComponent co, int height) {
Packit 4d380f
    co->height = height;
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
int newtTextboxGetNumLines(newtComponent co) {
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
Packit 4d380f
    return (tb->numLines);
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
newtComponent newtTextboxReflowed(int left, int top, char * text, int width,
Packit 4d380f
				  int flexDown, int flexUp, int flags) {
Packit 4d380f
    newtComponent co;
Packit 4d380f
    char * reflowedText;
Packit 4d380f
    int actWidth, actHeight;
Packit 4d380f
Packit 4d380f
    reflowedText = newtReflowText(text, width, flexDown, flexUp,
Packit 4d380f
				  &actWidth, &actHeight);
Packit 4d380f
    
Packit 4d380f
    co = newtTextbox(left, top, actWidth, actHeight, NEWT_FLAG_WRAP);
Packit 4d380f
    newtTextboxSetText(co, reflowedText);
Packit 4d380f
    free(reflowedText);
Packit 4d380f
Packit 4d380f
    return co;
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
newtComponent newtTextbox(int left, int top, int width, int height, int flags) {
Packit 4d380f
    newtComponent co;
Packit 4d380f
    struct textbox * tb;
Packit 4d380f
Packit 4d380f
    co = malloc(sizeof(*co));
Packit 4d380f
    tb = malloc(sizeof(*tb));
Packit 4d380f
    co->data = tb;
Packit 4d380f
Packit 4d380f
    if (width < 1)
Packit 4d380f
	width = 1;
Packit 4d380f
Packit 4d380f
    co->ops = &textboxOps;
Packit 4d380f
Packit 4d380f
    co->isMapped = 0;
Packit 4d380f
    co->height = height;
Packit 4d380f
    co->top = top;
Packit 4d380f
    co->left = left;
Packit 4d380f
    co->takesFocus = 0;
Packit 4d380f
    co->width = width;
Packit 4d380f
    co->destroyCallback = NULL;
Packit 4d380f
Packit 4d380f
    tb->doWrap = flags & NEWT_FLAG_WRAP;
Packit 4d380f
    tb->numLines = 0;
Packit 4d380f
    tb->linesAlloced = 0;
Packit 4d380f
    tb->lines = NULL;
Packit 4d380f
    tb->topLine = 0;
Packit 4d380f
    tb->textWidth = width;
Packit 4d380f
    tb->isActive = 0;
Packit 4d380f
    tb->cs = COLORSET_TEXTBOX;
Packit 4d380f
    tb->csActive = COLORSET_ACTTEXTBOX;
Packit 4d380f
Packit 4d380f
    if (flags & NEWT_FLAG_SCROLL) {
Packit 4d380f
	co->width += 2;
Packit 4d380f
	tb->sb = newtVerticalScrollbar(co->left + co->width - 1, co->top, 
Packit 4d380f
			   co->height, tb->cs, tb->cs);
Packit 4d380f
	co->takesFocus = 1;
Packit 4d380f
    } else {
Packit 4d380f
	tb->sb = NULL;
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    return co;
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
void newtTextboxSetColors(newtComponent co, int normal, int active) {
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
Packit 4d380f
    tb->cs = normal;
Packit 4d380f
    tb->csActive = active;
Packit 4d380f
    textboxDraw(co);
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
static char * expandTabs(const char * text) {
Packit 4d380f
    int bufAlloced = strlen(text) + 40;
Packit 4d380f
    char * buf, * dest;
Packit 4d380f
    const char * src;
Packit 4d380f
    int bufUsed = 0;
Packit 4d380f
    int linePos = 0;
Packit 4d380f
    int i;
Packit 4d380f
Packit 4d380f
    buf = malloc(bufAlloced + 1);
Packit 4d380f
    for (src = text, dest = buf; *src; src++) {
Packit 4d380f
	if ((bufUsed + 10) > bufAlloced) {
Packit 4d380f
	    bufAlloced += strlen(text) / 2;
Packit 4d380f
	    buf = realloc(buf, bufAlloced + 1);
Packit 4d380f
	    dest = buf + bufUsed;
Packit 4d380f
	}
Packit 4d380f
	if (*src == '\t') {
Packit 4d380f
	    i = 8 - (linePos & 8);
Packit 4d380f
	    memset(dest, ' ', i);
Packit 4d380f
	    dest += i, bufUsed += i, linePos += i;
Packit 4d380f
	} else {
Packit 4d380f
	    if (*src == '\n')
Packit 4d380f
		linePos = 0;
Packit 4d380f
	    else
Packit 4d380f
		linePos++;
Packit 4d380f
Packit 4d380f
	    *dest++ = *src;
Packit 4d380f
	    bufUsed++;
Packit 4d380f
	}
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    *dest = '\0';
Packit 4d380f
    return buf;
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
static void doReflow(const char * text, char ** resultPtr, int width, 
Packit 4d380f
		     int * badness, int * heightPtr) {
Packit 4d380f
    char * result = NULL;
Packit 4d380f
    const char * chptr, * end;
Packit 4d380f
    int i;
Packit 4d380f
    int howbad = 0;
Packit 4d380f
    int height = 0;
Packit 4d380f
    wchar_t tmp;
Packit 4d380f
    mbstate_t ps;
Packit 4d380f
Packit 4d380f
    if (resultPtr) {
Packit 4d380f
	if (width > 1) {
Packit 4d380f
	    /* use width - 1 for double width characters
Packit 4d380f
	       that won't fit at end of line */
Packit 4d380f
	    result = malloc(strlen(text) + (strlen(text) / (width - 1)) + 2);
Packit 4d380f
	} else
Packit 4d380f
	    result = malloc(strlen(text) * 2 + 2);
Packit 4d380f
	*resultPtr = result;
Packit 4d380f
    }
Packit 4d380f
	
Packit 4d380f
    memset(&ps,0,sizeof(mbstate_t));
Packit 4d380f
    while (*text) {
Packit 4d380f
	end = strchr(text, '\n');
Packit 4d380f
	if (!end)
Packit 4d380f
	    end = text + strlen(text);
Packit 4d380f
Packit 4d380f
	while (*text && text <= end) {
Packit 4d380f
	    int len;
Packit 4d380f
		
Packit 4d380f
	    len = wstrlen(text, end - text);
Packit 4d380f
	    if (len <= width) {
Packit 4d380f
		if (result) {
Packit 4d380f
		    memcpy(result, text, end - text);
Packit 4d380f
		    result += end - text;
Packit 4d380f
		    *result++ = '\n';
Packit 4d380f
		    height++;
Packit 4d380f
		}
Packit 4d380f
Packit 4d380f
		if (len < (width / 2)) {
Packit 4d380f
#ifdef DEBUG_WRAP		    
Packit 4d380f
		fprintf(stderr,"adding %d\n",((width / 2) - (len)) / 2);
Packit 4d380f
#endif					
Packit 4d380f
		    howbad += ((width / 2) - (len)) / 2;
Packit 4d380f
		}
Packit 4d380f
		text = end;
Packit 4d380f
		if (*text) text++;
Packit 4d380f
	    } else {
Packit 4d380f
		const char *spcptr = NULL;
Packit 4d380f
	        int spc = 0, w2 = 0, x, w;
Packit 4d380f
Packit 4d380f
	        chptr = text;
Packit 4d380f
		i = 0;
Packit 4d380f
		while (1) {
Packit 4d380f
			if ((x=mbrtowc(&tmp,chptr,end-chptr,&ps))<=0)
Packit 4d380f
				break;
Packit 4d380f
		        if (spc && !iswspace(tmp))
Packit 4d380f
				spc = 0;
Packit 4d380f
			else if (!spc && iswspace(tmp)) {
Packit 4d380f
				spc = 1;
Packit 4d380f
				spcptr = chptr;
Packit 4d380f
				w2 = i;
Packit 4d380f
			}
Packit 4d380f
			w = wcwidth(tmp);
Packit 4d380f
			if (w < 0)
Packit 4d380f
				w = 0;
Packit 4d380f
			if (i && w + i > width)
Packit 4d380f
				break;
Packit 4d380f
			chptr += x;
Packit 4d380f
			i += w;
Packit 4d380f
		}
Packit 4d380f
		howbad += width - w2 + 1;
Packit 4d380f
#ifdef DEBUG_WRAP		    
Packit 4d380f
		fprintf(stderr,"adding %d\n",width - w2 + 1, chptr);
Packit 4d380f
#endif					
Packit 4d380f
		if (spcptr) chptr = spcptr;
Packit 4d380f
		if (result) {
Packit 4d380f
		    memcpy(result, text, chptr - text);
Packit 4d380f
		    result += chptr - text;
Packit 4d380f
		    *result++ = '\n';
Packit 4d380f
		    height++;
Packit 4d380f
		}
Packit 4d380f
Packit 4d380f
		text = chptr;
Packit 4d380f
		while (1) {
Packit 4d380f
			if ((x=mbrtowc(&tmp,text,end-text,NULL))<=0)
Packit 4d380f
				break;
Packit 4d380f
			if (!iswspace(tmp)) break;
Packit 4d380f
			text += x;
Packit 4d380f
		}
Packit 4d380f
	    }
Packit 4d380f
	}
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    if (result)
Packit 4d380f
	*result = '\0';
Packit 4d380f
Packit 4d380f
    if (badness) *badness = howbad;
Packit 4d380f
    if (heightPtr) *heightPtr = height;
Packit 4d380f
#ifdef DEBUG_WRAP
Packit 4d380f
    fprintf(stderr, "width %d, badness %d, height %d\n",width, howbad, height);
Packit 4d380f
#endif
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
char * newtReflowText(char * text, int width, int flexDown, int flexUp,
Packit 4d380f
		      int * actualWidth, int * actualHeight) {
Packit 4d380f
    int min, max;
Packit 4d380f
    int i;
Packit 4d380f
    char * result;
Packit 4d380f
    int minbad, minbadwidth, howbad;
Packit 4d380f
    char * expandedText;
Packit 4d380f
Packit 4d380f
    if (width < 1)
Packit 4d380f
	width = 1;
Packit 4d380f
Packit 4d380f
    expandedText = expandTabs(text);
Packit 4d380f
Packit 4d380f
    if (flexDown || flexUp) {
Packit 4d380f
	min = width - flexDown;
Packit 4d380f
	max = width + flexUp;
Packit 4d380f
Packit 4d380f
	minbad = -1;
Packit 4d380f
	minbadwidth = width;
Packit 4d380f
Packit 4d380f
	for (i = min; i >= 1 && i <= max; i++) {
Packit 4d380f
	    doReflow(expandedText, NULL, i, &howbad, NULL);
Packit 4d380f
Packit 4d380f
	    if (minbad == -1 || howbad < minbad) {
Packit 4d380f
		minbad = howbad;
Packit 4d380f
		minbadwidth = i;
Packit 4d380f
	    }
Packit 4d380f
 	}
Packit 4d380f
Packit 4d380f
	width = minbadwidth;
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    doReflow(expandedText, &result, width, NULL, actualHeight);
Packit 4d380f
    free(expandedText);
Packit 4d380f
    if (actualWidth) *actualWidth = width;
Packit 4d380f
    return result;
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
void newtTextboxSetText(newtComponent co, const char * text) {
Packit 4d380f
    const char * start, * end;
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
    char * reflowed, * expanded;
Packit 4d380f
    int badness, height;
Packit 4d380f
Packit 4d380f
    if (tb->lines) {
Packit 4d380f
	int i;
Packit 4d380f
Packit 4d380f
	for (i = 0; i < tb->numLines; i++) 
Packit 4d380f
	    free(tb->lines[i]);
Packit 4d380f
	free(tb->lines);
Packit 4d380f
	tb->linesAlloced = tb->numLines = tb->topLine = 0;
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    expanded = expandTabs(text);
Packit 4d380f
Packit 4d380f
    if (tb->doWrap) {
Packit 4d380f
	doReflow(expanded, &reflowed, tb->textWidth, &badness, &height);
Packit 4d380f
	free(expanded);
Packit 4d380f
	expanded = reflowed;
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    for (start = expanded; *start; start++)
Packit 4d380f
	if (*start == '\n') tb->linesAlloced++;
Packit 4d380f
Packit 4d380f
    /* This ++ leaves room for an ending line w/o a \n */
Packit 4d380f
    tb->linesAlloced++;
Packit 4d380f
    tb->lines = malloc(sizeof(char *) * tb->linesAlloced);
Packit 4d380f
Packit 4d380f
    start = expanded;
Packit 4d380f
    while ((end = strchr(start, '\n'))) {
Packit 4d380f
	addLine(co, start, end - start);
Packit 4d380f
	start = end + 1;
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    if (*start)
Packit 4d380f
	addLine(co, start, strlen(start));
Packit 4d380f
Packit 4d380f
    free(expanded);
Packit 4d380f
    
Packit 4d380f
    textboxDraw(co);
Packit 4d380f
Packit 4d380f
    newtTrashScreen();    
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
/* This assumes the buffer is allocated properly! */
Packit 4d380f
static void addLine(newtComponent co, const char * s, int len) {
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
Packit 4d380f
    while (wstrlen(s,len) > tb->textWidth) {
Packit 4d380f
	    len--;
Packit 4d380f
    }
Packit 4d380f
    tb->lines[tb->numLines] = malloc(len + 1);
Packit 4d380f
    memcpy(tb->lines[tb->numLines], s, len);
Packit 4d380f
    tb->lines[tb->numLines++][len] = '\0';
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
static void textboxDraw(newtComponent c) {
Packit 4d380f
    int i;
Packit 4d380f
    struct textbox * tb = c->data;
Packit 4d380f
    int size;
Packit 4d380f
Packit 4d380f
    if (!c->isMapped)
Packit 4d380f
	    return;
Packit 4d380f
Packit 4d380f
    if (tb->sb) {
Packit 4d380f
	size = tb->numLines - c->height;
Packit 4d380f
	newtScrollbarSet(tb->sb, tb->topLine, size ? size : 0);
Packit 4d380f
	newtScrollbarSetColors(tb->sb, tb->isActive ? tb->csActive :
Packit 4d380f
			tb->cs, tb->cs);
Packit 4d380f
    }
Packit 4d380f
Packit 4d380f
    SLsmg_set_color(tb->cs);
Packit 4d380f
Packit 4d380f
    for (i = 0; (i + tb->topLine) < tb->numLines && i < c->height; i++) {
Packit 4d380f
	newtGotorc(c->top + i, c->left);
Packit 4d380f
	SLsmg_write_nstring(tb->lines[i + tb->topLine], tb->textWidth);
Packit 4d380f
    }
Packit 4d380f
    /* put cursor at beginning of text for better accessibility */
Packit 4d380f
    newtGotorc(c->top, c->left);
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
static struct eventResult textboxEvent(newtComponent co, 
Packit 4d380f
				      struct event ev) {
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
    struct eventResult er;
Packit 4d380f
Packit 4d380f
    er.result = ER_IGNORED;
Packit 4d380f
Packit 4d380f
    if (!tb->sb || ev.when == EV_EARLY || ev.when == EV_LATE)
Packit 4d380f
	return er;
Packit 4d380f
Packit 4d380f
    switch(ev.event) {
Packit 4d380f
      case EV_KEYPRESS:
Packit 4d380f
	newtTrashScreen();
Packit 4d380f
	switch (ev.u.key) {
Packit 4d380f
	  case NEWT_KEY_UP:
Packit 4d380f
	    if (tb->topLine) tb->topLine--;
Packit 4d380f
	    textboxDraw(co);
Packit 4d380f
	    er.result = ER_SWALLOWED;
Packit 4d380f
	    break;
Packit 4d380f
Packit 4d380f
	  case NEWT_KEY_DOWN:
Packit 4d380f
	    if (tb->topLine < (tb->numLines - co->height)) tb->topLine++;
Packit 4d380f
	    textboxDraw(co);
Packit 4d380f
	    er.result = ER_SWALLOWED;
Packit 4d380f
	    break;
Packit 4d380f
Packit 4d380f
	  case NEWT_KEY_PGDN:
Packit 4d380f
	    tb->topLine += co->height;
Packit 4d380f
	    if (tb->topLine > (tb->numLines - co->height)) {
Packit 4d380f
		tb->topLine = tb->numLines - co->height;
Packit 4d380f
		if (tb->topLine < 0) tb->topLine = 0;
Packit 4d380f
	    }
Packit 4d380f
	    textboxDraw(co);
Packit 4d380f
	    er.result = ER_SWALLOWED;
Packit 4d380f
	    break;
Packit 4d380f
Packit 4d380f
	  case NEWT_KEY_PGUP:
Packit 4d380f
	    tb->topLine -= co->height;
Packit 4d380f
	    if (tb->topLine < 0) tb->topLine = 0;
Packit 4d380f
	    textboxDraw(co);
Packit 4d380f
	    er.result = ER_SWALLOWED;
Packit 4d380f
	    break;
Packit 4d380f
	}
Packit 4d380f
	break;
Packit 4d380f
      case EV_MOUSE:
Packit 4d380f
	/* Top scroll arrow */
Packit 4d380f
	if (ev.u.mouse.x == co->width && ev.u.mouse.y == co->top) {
Packit 4d380f
	    if (tb->topLine) tb->topLine--;
Packit 4d380f
	    textboxDraw(co);
Packit 4d380f
	    
Packit 4d380f
	    er.result = ER_SWALLOWED;
Packit 4d380f
	}
Packit 4d380f
	/* Bottom scroll arrow */
Packit 4d380f
	if (ev.u.mouse.x == co->width &&
Packit 4d380f
	    ev.u.mouse.y == co->top + co->height - 1) {
Packit 4d380f
	    if (tb->topLine < (tb->numLines - co->height)) tb->topLine++;
Packit 4d380f
	    textboxDraw(co);
Packit 4d380f
	    
Packit 4d380f
	    er.result = ER_SWALLOWED;
Packit 4d380f
	}
Packit 4d380f
	break;
Packit 4d380f
      case EV_FOCUS:
Packit 4d380f
	tb->isActive = 1;
Packit 4d380f
	textboxDraw(co);
Packit 4d380f
	er.result = ER_SWALLOWED;
Packit 4d380f
	break;
Packit 4d380f
      case EV_UNFOCUS:
Packit 4d380f
	tb->isActive = 0;
Packit 4d380f
	textboxDraw(co);
Packit 4d380f
	er.result = ER_SWALLOWED;
Packit 4d380f
	break;
Packit 4d380f
    }
Packit 4d380f
    return er;
Packit 4d380f
}
Packit 4d380f
Packit 4d380f
static void textboxDestroy(newtComponent co) {
Packit 4d380f
    int i;
Packit 4d380f
    struct textbox * tb = co->data;
Packit 4d380f
Packit 4d380f
    if (tb->sb)
Packit 4d380f
	tb->sb->ops->destroy(tb->sb);
Packit 4d380f
    for (i = 0; i < tb->numLines; i++) 
Packit 4d380f
	free(tb->lines[i]);
Packit 4d380f
    free(tb->lines);
Packit 4d380f
    free(tb);
Packit 4d380f
    free(co);
Packit 4d380f
}