]>
Commit | Line | Data |
---|---|---|
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 | ||
12 | struct 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 | 25 | static char * expandTabs(const char * text); |
20476098 | 26 | static void textboxDraw(newtComponent co); |
aa2a62d9 | 27 | static void addLine(newtComponent co, const char * s, int len); |
28 | static void doReflow(const char * text, char ** resultPtr, int width, | |
6f481af2 | 29 | int * badness, int * heightPtr); |
45c366b1 | 30 | static struct eventResult textboxEvent(newtComponent c, |
6f481af2 | 31 | struct event ev); |
20476098 | 32 | static void textboxDestroy(newtComponent co); |
8f52cd47 | 33 | static void textboxPlace(newtComponent co, int newLeft, int newTop); |
34 | static void textboxMapped(newtComponent co, int isMapped); | |
20476098 | 35 | |
36 | static struct componentOps textboxOps = { | |
37 | textboxDraw, | |
38 | textboxEvent, | |
39 | textboxDestroy, | |
8f52cd47 | 40 | textboxPlace, |
41 | textboxMapped, | |
20476098 | 42 | } ; |
43 | ||
8f52cd47 | 44 | static 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 | ||
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 | ||
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 | 64 | void newtTextboxSetHeight(newtComponent co, int height) { |
65 | co->height = height; | |
66 | } | |
67 | ||
68 | int newtTextboxGetNumLines(newtComponent co) { | |
69 | struct textbox * tb = co->data; | |
70 | ||
71 | return (tb->numLines); | |
72 | } | |
73 | ||
f1d6b549 | 74 | newtComponent 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 | 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 | ||
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 |
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 | ||
aa2a62d9 | 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; | |
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 | ||
175 | static 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 | ||
278 | char * 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 | 316 | void 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! */ |
363 | static 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 | ||
374 | static 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 | 399 | static 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 | ||
474 | static 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 | } |