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