]> git.ipfire.org Git - thirdparty/newt.git/blame - textbox.c
0.52.24
[thirdparty/newt.git] / textbox.c
CommitLineData
349586bb 1
20476098 2#include <ctype.h>
139f06bc 3#include <slang.h>
20476098 4#include <stdlib.h>
05130221 5#include <string.h>
349586bb 6#include <wchar.h>
7#include <wctype.h>
20476098 8
9#include "newt.h"
10#include "newt_pr.h"
11
12struct textbox {
13 char ** lines;
14 int numLines;
15 int linesAlloced;
16 int doWrap;
099b22e5 17 newtComponent sb;
20476098 18 int topLine;
8f52cd47 19 int textWidth;
9fc6a6b0 20 int isActive;
099b22e5
ML
21 int cs;
22 int csActive;
20476098 23};
24
aa2a62d9 25static char * expandTabs(const char * text);
20476098 26static void textboxDraw(newtComponent co);
aa2a62d9 27static void addLine(newtComponent co, const char * s, int len);
28static void doReflow(const char * text, char ** resultPtr, int width,
6f481af2 29 int * badness, int * heightPtr);
45c366b1 30static struct eventResult textboxEvent(newtComponent c,
6f481af2 31 struct event ev);
20476098 32static void textboxDestroy(newtComponent co);
8f52cd47 33static void textboxPlace(newtComponent co, int newLeft, int newTop);
34static void textboxMapped(newtComponent co, int isMapped);
20476098 35
36static struct componentOps textboxOps = {
37 textboxDraw,
38 textboxEvent,
39 textboxDestroy,
8f52cd47 40 textboxPlace,
41 textboxMapped,
20476098 42} ;
43
8f52cd47 44static void textboxMapped(newtComponent co, int isMapped) {
45 struct textbox * tb = co->data;
46
47 co->isMapped = isMapped;
9fc6a6b0 48 if (tb->sb) {
6f481af2 49 tb->sb->ops->mapped(tb->sb, isMapped);
9fc6a6b0 50 }
8f52cd47 51}
52
53static void textboxPlace(newtComponent co, int newLeft, int newTop) {
54 struct textbox * tb = co->data;
55
56 co->top = newTop;
57 co->left = newLeft;
58
9fc6a6b0 59 if (tb->sb) {
6f481af2 60 tb->sb->ops->place(tb->sb, co->left + co->width - 1, co->top);
9fc6a6b0 61 }
8f52cd47 62}
63
4b5d9a60 64void newtTextboxSetHeight(newtComponent co, int height) {
65 co->height = height;
66}
67
68int newtTextboxGetNumLines(newtComponent co) {
69 struct textbox * tb = co->data;
70
71 return (tb->numLines);
72}
73
f1d6b549 74newtComponent newtTextboxReflowed(int left, int top, char * text, int width,
6f481af2 75 int flexDown, int flexUp, int flags) {
f1d6b549 76 newtComponent co;
77 char * reflowedText;
78 int actWidth, actHeight;
79
80 reflowedText = newtReflowText(text, width, flexDown, flexUp,
6f481af2 81 &actWidth, &actHeight);
f1d6b549 82
5fab4758 83 co = newtTextbox(left, top, actWidth, actHeight, NEWT_FLAG_WRAP);
f1d6b549 84 newtTextboxSetText(co, reflowedText);
85 free(reflowedText);
86
87 return co;
88}
89
20476098 90newtComponent 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
24df6719
ML
98 if (width < 1)
99 width = 1;
feef2cb5 100
20476098 101 co->ops = &textboxOps;
102
0f6b2c8b 103 co->isMapped = 0;
20476098 104 co->height = height;
105 co->top = top;
106 co->left = left;
107 co->takesFocus = 0;
8f52cd47 108 co->width = width;
c101e99e 109 co->destroyCallback = NULL;
20476098 110
4a93351d 111 tb->doWrap = flags & NEWT_FLAG_WRAP;
20476098 112 tb->numLines = 0;
113 tb->linesAlloced = 0;
114 tb->lines = NULL;
115 tb->topLine = 0;
8f52cd47 116 tb->textWidth = width;
9fc6a6b0 117 tb->isActive = 0;
099b22e5
ML
118 tb->cs = COLORSET_TEXTBOX;
119 tb->csActive = COLORSET_ACTTEXTBOX;
20476098 120
4a93351d 121 if (flags & NEWT_FLAG_SCROLL) {
6f481af2 122 co->width += 2;
123 tb->sb = newtVerticalScrollbar(co->left + co->width - 1, co->top,
099b22e5 124 co->height, tb->cs, tb->cs);
9fc6a6b0 125 co->takesFocus = 1;
20476098 126 } else {
099b22e5 127 tb->sb = NULL;
20476098 128 }
129
130 return co;
131}
132
099b22e5
ML
133void 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
aa2a62d9 141static char * expandTabs(const char * text) {
142 int bufAlloced = strlen(text) + 40;
143 char * buf, * dest;
144 const char * src;
145 int bufUsed = 0;
48f7f39d 146 int linePos = 0;
aa2a62d9 147 int i;
148
149 buf = malloc(bufAlloced + 1);
150 for (src = text, dest = buf; *src; src++) {
6f481af2 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 }
aa2a62d9 169 }
170
171 *dest = '\0';
172 return buf;
173}
174
175static void doReflow(const char * text, char ** resultPtr, int width,
6f481af2 176 int * badness, int * heightPtr) {
a7582573 177 char * result = NULL;
aa2a62d9 178 const char * chptr, * end;
771c47c0 179 int i;
a7582573 180 int howbad = 0;
181 int height = 0;
349586bb 182 wchar_t tmp;
183 mbstate_t ps;
a7582573 184
185 if (resultPtr) {
24df6719
ML
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);
8aeac5da 192 *resultPtr = result;
a7582573 193 }
6f481af2 194
349586bb 195 memset(&ps,0,sizeof(mbstate_t));
a7582573 196 while (*text) {
6f481af2 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);
b16ddee7 205 if (len <= width) {
6f481af2 206 if (result) {
8aeac5da
ML
207 memcpy(result, text, end - text);
208 result += end - text;
209 *result++ = '\n';
6f481af2 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;
b16ddee7 223 int spc = 0, w2 = 0, x, w;
6f481af2 224
225 chptr = text;
b16ddee7
ML
226 i = 0;
227 while (1) {
6f481af2 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 }
b16ddee7
ML
237 w = wcwidth(tmp);
238 if (w < 0)
239 w = 0;
240 if (i && w + i > width)
241 break;
6f481af2 242 chptr += x;
b16ddee7 243 i += w;
6f481af2 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) {
8aeac5da
ML
251 memcpy(result, text, chptr - text);
252 result += chptr - text;
253 *result++ = '\n';
6f481af2 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 }
63231242 266 }
267
8aeac5da
ML
268 if (result)
269 *result = '\0';
270
a7582573 271 if (badness) *badness = howbad;
a7582573 272 if (heightPtr) *heightPtr = height;
349586bb 273#ifdef DEBUG_WRAP
274 fprintf(stderr, "width %d, badness %d, height %d\n",width, howbad, height);
275#endif
a7582573 276}
277
278char * newtReflowText(char * text, int width, int flexDown, int flexUp,
6f481af2 279 int * actualWidth, int * actualHeight) {
a7582573 280 int min, max;
281 int i;
282 char * result;
283 int minbad, minbadwidth, howbad;
aa2a62d9 284 char * expandedText;
285
24df6719
ML
286 if (width < 1)
287 width = 1;
288
aa2a62d9 289 expandedText = expandTabs(text);
a7582573 290
291 if (flexDown || flexUp) {
6f481af2 292 min = width - flexDown;
293 max = width + flexUp;
a7582573 294
6f481af2 295 minbad = -1;
296 minbadwidth = width;
a7582573 297
24df6719 298 for (i = min; i >= 1 && i <= max; i++) {
6f481af2 299 doReflow(expandedText, NULL, i, &howbad, NULL);
a7582573 300
6f481af2 301 if (minbad == -1 || howbad < minbad) {
302 minbad = howbad;
303 minbadwidth = i;
304 }
305 }
a7582573 306
6f481af2 307 width = minbadwidth;
a7582573 308 }
309
aa2a62d9 310 doReflow(expandedText, &result, width, NULL, actualHeight);
311 free(expandedText);
a7582573 312 if (actualWidth) *actualWidth = width;
313 return result;
314}
315
20476098 316void newtTextboxSetText(newtComponent co, const char * text) {
317 const char * start, * end;
318 struct textbox * tb = co->data;
aa2a62d9 319 char * reflowed, * expanded;
320 int badness, height;
20476098 321
322 if (tb->lines) {
31fdb87e
ML
323 int i;
324
325 for (i = 0; i < tb->numLines; i++)
326 free(tb->lines[i]);
6f481af2 327 free(tb->lines);
ec535e36 328 tb->linesAlloced = tb->numLines = tb->topLine = 0;
20476098 329 }
330
aa2a62d9 331 expanded = expandTabs(text);
332
333 if (tb->doWrap) {
6f481af2 334 doReflow(expanded, &reflowed, tb->textWidth, &badness, &height);
335 free(expanded);
336 expanded = reflowed;
aa2a62d9 337 }
338
339 for (start = expanded; *start; start++)
6f481af2 340 if (*start == '\n') tb->linesAlloced++;
aa2a62d9 341
342 /* This ++ leaves room for an ending line w/o a \n */
343 tb->linesAlloced++;
344 tb->lines = malloc(sizeof(char *) * tb->linesAlloced);
20476098 345
aa2a62d9 346 start = expanded;
20476098 347 while ((end = strchr(start, '\n'))) {
6f481af2 348 addLine(co, start, end - start);
349 start = end + 1;
20476098 350 }
351
352 if (*start)
6f481af2 353 addLine(co, start, strlen(start));
20476098 354
aa2a62d9 355 free(expanded);
add984c1 356
0f6b2c8b
ML
357 textboxDraw(co);
358
add984c1 359 newtTrashScreen();
20476098 360}
361
aa2a62d9 362/* This assumes the buffer is allocated properly! */
363static void addLine(newtComponent co, const char * s, int len) {
20476098 364 struct textbox * tb = co->data;
365
349586bb 366 while (wstrlen(s,len) > tb->textWidth) {
6f481af2 367 len--;
349586bb 368 }
369 tb->lines[tb->numLines] = malloc(len + 1);
aa2a62d9 370 memcpy(tb->lines[tb->numLines], s, len);
349586bb 371 tb->lines[tb->numLines++][len] = '\0';
20476098 372}
373
374static void textboxDraw(newtComponent c) {
375 int i;
376 struct textbox * tb = c->data;
377 int size;
10de8f4a 378
0f6b2c8b
ML
379 if (!c->isMapped)
380 return;
381
20476098 382 if (tb->sb) {
6f481af2 383 size = tb->numLines - c->height;
099b22e5
ML
384 newtScrollbarSet(tb->sb, tb->topLine, size ? size : 0);
385 newtScrollbarSetColors(tb->sb, tb->isActive ? tb->csActive :
386 tb->cs, tb->cs);
20476098 387 }
676cfb93 388
099b22e5 389 SLsmg_set_color(tb->cs);
7ff5fd71 390
20476098 391 for (i = 0; (i + tb->topLine) < tb->numLines && i < c->height; i++) {
6f481af2 392 newtGotorc(c->top + i, c->left);
fed9b66a 393 SLsmg_write_nstring(tb->lines[i + tb->topLine], tb->textWidth);
20476098 394 }
4e667865 395 /* put cursor at beginning of text for better accessibility */
396 newtGotorc(c->top, c->left);
20476098 397}
398
45c366b1 399static struct eventResult textboxEvent(newtComponent co,
6f481af2 400 struct event ev) {
20476098 401 struct textbox * tb = co->data;
402 struct eventResult er;
403
404 er.result = ER_IGNORED;
405
9fc6a6b0 406 if (!tb->sb || ev.when == EV_EARLY || ev.when == EV_LATE)
407 return er;
408
409 switch(ev.event) {
410 case EV_KEYPRESS:
6f481af2 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 }
9fc6a6b0 442 break;
443 case EV_MOUSE:
6f481af2 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 }
9fc6a6b0 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;
45f6c4fd 470 }
20476098 471 return er;
472}
473
474static void textboxDestroy(newtComponent co) {
475 int i;
476 struct textbox * tb = co->data;
477
f7a354ed 478 if (tb->sb)
479 tb->sb->ops->destroy(tb->sb);
20476098 480 for (i = 0; i < tb->numLines; i++)
6f481af2 481 free(tb->lines[i]);
20476098 482 free(tb->lines);
483 free(tb);
484 free(co);
485}