1 
2 #include <ctype.h>
3 #include <slang.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <wchar.h>
7 #include <wctype.h>
8 
9 #include "newt.h"
10 #include "newt_pr.h"
11 
12 struct textbox {
13     char ** lines;
14     int numLines;
15     int linesAlloced;
16     int doWrap;
17     newtComponent sb;
18     int topLine;
19     int textWidth;
20     int isActive;
21     int cs;
22     int csActive;
23 };
24 
25 static char * expandTabs(const char * text);
26 static void textboxDraw(newtComponent co);
27 static void addLine(newtComponent co, const char * s, int len);
28 static void doReflow(const char * text, char ** resultPtr, int width,
29 		     int * badness, int * heightPtr);
30 static struct eventResult textboxEvent(newtComponent c,
31 				      struct event ev);
32 static void textboxDestroy(newtComponent co);
33 static void textboxPlace(newtComponent co, int newLeft, int newTop);
34 static void textboxMapped(newtComponent co, int isMapped);
35 
36 static struct componentOps textboxOps = {
37     textboxDraw,
38     textboxEvent,
39     textboxDestroy,
40     textboxPlace,
41     textboxMapped,
42 } ;
43 
textboxMapped(newtComponent co,int isMapped)44 static void textboxMapped(newtComponent co, int isMapped) {
45     struct textbox * tb = co->data;
46 
47     co->isMapped = isMapped;
48     if (tb->sb) {
49 	tb->sb->ops->mapped(tb->sb, isMapped);
50     }
51 }
52 
textboxPlace(newtComponent co,int newLeft,int newTop)53 static void textboxPlace(newtComponent co, int newLeft, int newTop) {
54     struct textbox * tb = co->data;
55 
56     co->top = newTop;
57     co->left = newLeft;
58 
59     if (tb->sb) {
60 	tb->sb->ops->place(tb->sb, co->left + co->width - 1, co->top);
61     }
62 }
63 
newtTextboxSetHeight(newtComponent co,int height)64 void newtTextboxSetHeight(newtComponent co, int height) {
65     co->height = height;
66 }
67 
newtTextboxGetNumLines(newtComponent co)68 int newtTextboxGetNumLines(newtComponent co) {
69     struct textbox * tb = co->data;
70 
71     return (tb->numLines);
72 }
73 
newtTextboxReflowed(int left,int top,char * text,int width,int flexDown,int flexUp,int flags)74 newtComponent newtTextboxReflowed(int left, int top, char * text, int width,
75 				  int flexDown, int flexUp, int flags) {
76     newtComponent co;
77     char * reflowedText;
78     int actWidth, actHeight;
79 
80     reflowedText = newtReflowText(text, width, flexDown, flexUp,
81 				  &actWidth, &actHeight);
82 
83     co = newtTextbox(left, top, actWidth, actHeight, NEWT_FLAG_WRAP);
84     newtTextboxSetText(co, reflowedText);
85     free(reflowedText);
86 
87     return co;
88 }
89 
newtTextbox(int left,int top,int width,int height,int flags)90 newtComponent newtTextbox(int left, int top, int width, int height, int flags) {
91     newtComponent co;
92     struct textbox * tb;
93 
94     co = malloc(sizeof(*co));
95     tb = malloc(sizeof(*tb));
96     co->data = tb;
97 
98     if (width < 1)
99 	width = 1;
100 
101     co->ops = &textboxOps;
102 
103     co->isMapped = 0;
104     co->height = height;
105     co->top = top;
106     co->left = left;
107     co->takesFocus = 0;
108     co->width = width;
109     co->destroyCallback = NULL;
110 
111     tb->doWrap = flags & NEWT_FLAG_WRAP;
112     tb->numLines = 0;
113     tb->linesAlloced = 0;
114     tb->lines = NULL;
115     tb->topLine = 0;
116     tb->textWidth = width;
117     tb->isActive = 0;
118     tb->cs = COLORSET_TEXTBOX;
119     tb->csActive = COLORSET_ACTTEXTBOX;
120 
121     if (flags & NEWT_FLAG_SCROLL) {
122 	co->width += 2;
123 	tb->sb = newtVerticalScrollbar(co->left + co->width - 1, co->top,
124 			   co->height, tb->cs, tb->cs);
125 	co->takesFocus = 1;
126     } else {
127 	tb->sb = NULL;
128     }
129 
130     return co;
131 }
132 
newtTextboxSetColors(newtComponent co,int normal,int active)133 void newtTextboxSetColors(newtComponent co, int normal, int active) {
134     struct textbox * tb = co->data;
135 
136     tb->cs = normal;
137     tb->csActive = active;
138     textboxDraw(co);
139 }
140 
expandTabs(const char * text)141 static char * expandTabs(const char * text) {
142     int bufAlloced = strlen(text) + 40;
143     char * buf, * dest;
144     const char * src;
145     int bufUsed = 0;
146     int linePos = 0;
147     int i;
148 
149     buf = malloc(bufAlloced + 1);
150     for (src = text, dest = buf; *src; src++) {
151 	if ((bufUsed + 10) > bufAlloced) {
152 	    bufAlloced += strlen(text) / 2;
153 	    buf = realloc(buf, bufAlloced + 1);
154 	    dest = buf + bufUsed;
155 	}
156 	if (*src == '\t') {
157 	    i = 8 - (linePos & 8);
158 	    memset(dest, ' ', i);
159 	    dest += i, bufUsed += i, linePos += i;
160 	} else {
161 	    if (*src == '\n')
162 		linePos = 0;
163 	    else
164 		linePos++;
165 
166 	    *dest++ = *src;
167 	    bufUsed++;
168 	}
169     }
170 
171     *dest = '\0';
172     return buf;
173 }
174 
doReflow(const char * text,char ** resultPtr,int width,int * badness,int * heightPtr)175 static void doReflow(const char * text, char ** resultPtr, int width,
176 		     int * badness, int * heightPtr) {
177     char * result = NULL;
178     const char * chptr, * end;
179     int i;
180     int howbad = 0;
181     int height = 0;
182     wchar_t tmp;
183     mbstate_t ps;
184 
185     if (resultPtr) {
186 	if (width > 1) {
187 	    /* use width - 1 for double width characters
188 	       that won't fit at end of line */
189 	    result = malloc(strlen(text) + (strlen(text) / (width - 1)) + 2);
190 	} else
191 	    result = malloc(strlen(text) * 2 + 2);
192 	*resultPtr = result;
193     }
194 
195     memset(&ps,0,sizeof(mbstate_t));
196     while (*text) {
197 	end = strchr(text, '\n');
198 	if (!end)
199 	    end = text + strlen(text);
200 
201 	while (*text && text <= end) {
202 	    int len;
203 
204 	    len = wstrlen(text, end - text);
205 	    if (len <= width) {
206 		if (result) {
207 		    memcpy(result, text, end - text);
208 		    result += end - text;
209 		    *result++ = '\n';
210 		    height++;
211 		}
212 
213 		if (len < (width / 2)) {
214 #ifdef DEBUG_WRAP
215 		fprintf(stderr,"adding %d\n",((width / 2) - (len)) / 2);
216 #endif
217 		    howbad += ((width / 2) - (len)) / 2;
218 		}
219 		text = end;
220 		if (*text) text++;
221 	    } else {
222 		const char *spcptr = NULL;
223 	        int spc = 0, w2 = 0, x, w;
224 
225 	        chptr = text;
226 		i = 0;
227 		while (1) {
228 			if ((x=mbrtowc(&tmp,chptr,end-chptr,&ps))<=0)
229 				break;
230 		        if (spc && !iswspace(tmp))
231 				spc = 0;
232 			else if (!spc && iswspace(tmp)) {
233 				spc = 1;
234 				spcptr = chptr;
235 				w2 = i;
236 			}
237 			w = wcwidth(tmp);
238 			if (w < 0)
239 				w = 0;
240 			if (i && w + i > width)
241 				break;
242 			chptr += x;
243 			i += w;
244 		}
245 		howbad += width - w2 + 1;
246 #ifdef DEBUG_WRAP
247 		fprintf(stderr,"adding %d\n",width - w2 + 1, chptr);
248 #endif
249 		if (spcptr) chptr = spcptr;
250 		if (result) {
251 		    memcpy(result, text, chptr - text);
252 		    result += chptr - text;
253 		    *result++ = '\n';
254 		    height++;
255 		}
256 
257 		text = chptr;
258 		while (1) {
259 			if ((x=mbrtowc(&tmp,text,end-text,NULL))<=0)
260 				break;
261 			if (!iswspace(tmp)) break;
262 			text += x;
263 		}
264 	    }
265 	}
266     }
267 
268     if (result)
269 	*result = '\0';
270 
271     if (badness) *badness = howbad;
272     if (heightPtr) *heightPtr = height;
273 #ifdef DEBUG_WRAP
274     fprintf(stderr, "width %d, badness %d, height %d\n",width, howbad, height);
275 #endif
276 }
277 
newtReflowText(char * text,int width,int flexDown,int flexUp,int * actualWidth,int * actualHeight)278 char * newtReflowText(char * text, int width, int flexDown, int flexUp,
279 		      int * actualWidth, int * actualHeight) {
280     int min, max;
281     int i;
282     char * result;
283     int minbad, minbadwidth, howbad;
284     char * expandedText;
285 
286     if (width < 1)
287 	width = 1;
288 
289     expandedText = expandTabs(text);
290 
291     if (flexDown || flexUp) {
292 	min = width - flexDown;
293 	max = width + flexUp;
294 
295 	minbad = -1;
296 	minbadwidth = width;
297 
298 	for (i = min; i >= 1 && i <= max; i++) {
299 	    doReflow(expandedText, NULL, i, &howbad, NULL);
300 
301 	    if (minbad == -1 || howbad < minbad) {
302 		minbad = howbad;
303 		minbadwidth = i;
304 	    }
305  	}
306 
307 	width = minbadwidth;
308     }
309 
310     doReflow(expandedText, &result, width, NULL, actualHeight);
311     free(expandedText);
312     if (actualWidth) *actualWidth = width;
313     return result;
314 }
315 
newtTextboxSetText(newtComponent co,const char * text)316 void newtTextboxSetText(newtComponent co, const char * text) {
317     const char * start, * end;
318     struct textbox * tb = co->data;
319     char * reflowed, * expanded;
320     int badness, height;
321 
322     if (tb->lines) {
323 	int i;
324 
325 	for (i = 0; i < tb->numLines; i++)
326 	    free(tb->lines[i]);
327 	free(tb->lines);
328 	tb->linesAlloced = tb->numLines = tb->topLine = 0;
329     }
330 
331     expanded = expandTabs(text);
332 
333     if (tb->doWrap) {
334 	doReflow(expanded, &reflowed, tb->textWidth, &badness, &height);
335 	free(expanded);
336 	expanded = reflowed;
337     }
338 
339     for (start = expanded; *start; start++)
340 	if (*start == '\n') tb->linesAlloced++;
341 
342     /* This ++ leaves room for an ending line w/o a \n */
343     tb->linesAlloced++;
344     tb->lines = malloc(sizeof(char *) * tb->linesAlloced);
345 
346     start = expanded;
347     while ((end = strchr(start, '\n'))) {
348 	addLine(co, start, end - start);
349 	start = end + 1;
350     }
351 
352     if (*start)
353 	addLine(co, start, strlen(start));
354 
355     free(expanded);
356 
357     textboxDraw(co);
358 
359     newtTrashScreen();
360 }
361 
362 /* This assumes the buffer is allocated properly! */
addLine(newtComponent co,const char * s,int len)363 static void addLine(newtComponent co, const char * s, int len) {
364     struct textbox * tb = co->data;
365 
366     while (wstrlen(s,len) > tb->textWidth) {
367 	    len--;
368     }
369     tb->lines[tb->numLines] = malloc(len + 1);
370     memcpy(tb->lines[tb->numLines], s, len);
371     tb->lines[tb->numLines++][len] = '\0';
372 }
373 
textboxDraw(newtComponent c)374 static void textboxDraw(newtComponent c) {
375     int i;
376     struct textbox * tb = c->data;
377     int size;
378 
379     if (!c->isMapped)
380 	    return;
381 
382     if (tb->sb) {
383 	size = tb->numLines - c->height;
384 	newtScrollbarSet(tb->sb, tb->topLine, size ? size : 0);
385 	newtScrollbarSetColors(tb->sb, tb->isActive ? tb->csActive :
386 			tb->cs, tb->cs);
387     }
388 
389     SLsmg_set_color(tb->cs);
390 
391     for (i = 0; (i + tb->topLine) < tb->numLines && i < c->height; i++) {
392 	newtGotorc(c->top + i, c->left);
393 	SLsmg_write_nstring(tb->lines[i + tb->topLine], tb->textWidth);
394     }
395     /* put cursor at beginning of text for better accessibility */
396     newtGotorc(c->top, c->left);
397 }
398 
textboxEvent(newtComponent co,struct event ev)399 static struct eventResult textboxEvent(newtComponent co,
400 				      struct event ev) {
401     struct textbox * tb = co->data;
402     struct eventResult er;
403 
404     er.result = ER_IGNORED;
405 
406     if (!tb->sb || ev.when == EV_EARLY || ev.when == EV_LATE)
407 	return er;
408 
409     switch(ev.event) {
410       case EV_KEYPRESS:
411 	newtTrashScreen();
412 	switch (ev.u.key) {
413 	  case NEWT_KEY_UP:
414 	    if (tb->topLine) tb->topLine--;
415 	    textboxDraw(co);
416 	    er.result = ER_SWALLOWED;
417 	    break;
418 
419 	  case NEWT_KEY_DOWN:
420 	    if (tb->topLine < (tb->numLines - co->height)) tb->topLine++;
421 	    textboxDraw(co);
422 	    er.result = ER_SWALLOWED;
423 	    break;
424 
425 	  case NEWT_KEY_PGDN:
426 	    tb->topLine += co->height;
427 	    if (tb->topLine > (tb->numLines - co->height)) {
428 		tb->topLine = tb->numLines - co->height;
429 		if (tb->topLine < 0) tb->topLine = 0;
430 	    }
431 	    textboxDraw(co);
432 	    er.result = ER_SWALLOWED;
433 	    break;
434 
435 	  case NEWT_KEY_PGUP:
436 	    tb->topLine -= co->height;
437 	    if (tb->topLine < 0) tb->topLine = 0;
438 	    textboxDraw(co);
439 	    er.result = ER_SWALLOWED;
440 	    break;
441 	}
442 	break;
443       case EV_MOUSE:
444 	/* Top scroll arrow */
445 	if (ev.u.mouse.x == co->width && ev.u.mouse.y == co->top) {
446 	    if (tb->topLine) tb->topLine--;
447 	    textboxDraw(co);
448 
449 	    er.result = ER_SWALLOWED;
450 	}
451 	/* Bottom scroll arrow */
452 	if (ev.u.mouse.x == co->width &&
453 	    ev.u.mouse.y == co->top + co->height - 1) {
454 	    if (tb->topLine < (tb->numLines - co->height)) tb->topLine++;
455 	    textboxDraw(co);
456 
457 	    er.result = ER_SWALLOWED;
458 	}
459 	break;
460       case EV_FOCUS:
461 	tb->isActive = 1;
462 	textboxDraw(co);
463 	er.result = ER_SWALLOWED;
464 	break;
465       case EV_UNFOCUS:
466 	tb->isActive = 0;
467 	textboxDraw(co);
468 	er.result = ER_SWALLOWED;
469 	break;
470     }
471     return er;
472 }
473 
textboxDestroy(newtComponent co)474 static void textboxDestroy(newtComponent co) {
475     int i;
476     struct textbox * tb = co->data;
477 
478     if (tb->sb)
479 	tb->sb->ops->destroy(tb->sb);
480     for (i = 0; i < tb->numLines; i++)
481 	free(tb->lines[i]);
482     free(tb->lines);
483     free(tb);
484     free(co);
485 }
486