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