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