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