- Now splits paragraphs into multiple lines, with each line fitting the
[AROS.git] / workbench / libs / muimaster / classes / floattext.c
blob7b3e2c1f256d514848ffb1b9160240e719439b35
1 /*
2 Copyright © 2002-2012, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 #define MUIMASTER_YES_INLINE_STDARG
8 #define DEBUG 0
9 #include <aros/debug.h>
11 #include <exec/memory.h>
12 #include <clib/alib_protos.h>
13 #include <proto/exec.h>
14 #include <proto/dos.h>
15 #include <proto/intuition.h>
16 #include <proto/utility.h>
17 #include <proto/muimaster.h>
18 #include <proto/graphics.h>
20 #include <string.h>
22 #include "mui.h"
23 #include "muimaster_intern.h"
24 #include "support.h"
25 #include "support_classes.h"
26 #include "floattext_private.h"
28 extern struct Library *MUIMasterBase;
30 // like strlen(), but \n ends string, too.
31 static long MyStrLen(const char *ptr)
33 const char *start = ptr;
35 while (*ptr && (*ptr != '\n'))
36 ptr++;
38 return (((long)ptr) - ((long)start));
42 * Calculates the number of characters that will fit on a line of a given
43 * pixel width.
45 static UWORD FitParagraphLine(STRPTR text, ULONG length, WORD width,
46 ULONG offset, struct Window *window)
48 struct TextExtent temp_extent;
49 ULONG char_count;
50 UBYTE *p, *q = text + offset;
52 char_count = TextFit(window->RPort, text + offset,
53 length - offset, &temp_extent, NULL, 1, width, 32767);
55 /* Find the last space in the fitted substring */
57 if (offset + char_count != length)
59 for (p = text + offset + char_count; p > q && *p != ' '; p--);
60 for (; p > q && *p == ' '; p--);
61 if (p > q)
62 char_count = p - q + 1;
65 return char_count;
68 static void SetText(Object *obj, struct Floattext_DATA *data)
70 WORD width;
71 struct Window *window;
72 UWORD i, count, pos, line_size, space_count, extra_space_count,
73 space_width, space_multiple, bonus_space_count,
74 bonus_space_mod, space_no_in, space_no_out, stripped_pos,
75 stripped_count, control_count, tab_count;
76 UBYTE *text, *p, *q, *r, c, *line = NULL, *stripped_text = NULL;
77 LONG len, stripped_len;
78 BOOL justify, found, is_empty;
80 /* Avoid redrawing list while inserting lines */
82 data->typesetting = TRUE;
84 DoMethod(obj, MUIM_List_Clear);
86 window = (struct Window *)XGET(obj, MUIA_Window);
87 width = XGET(obj, MUIA_Width) - XGET(obj, MUIA_InnerLeft)
88 - XGET(obj, MUIA_InnerRight) - 8;
90 /* Only do layout if we have some text and a window in which to put it */
92 if (data->text && window != NULL)
94 /* Get width of a space character */
96 space_width = TextLength(window->RPort, " ", 1);
98 /* Lay out each paragraph */
100 for (text = data->text; text[0] != '\0'; text += pos)
102 stripped_pos = pos = 0;
103 len = MyStrLen(text);
105 if (len > 0)
107 /* Allocate line/paragraph buffers */
109 if (line == NULL || len >= line_size)
111 FreeVec(line);
112 line = AllocVec(len + 1, MEMF_ANY);
113 stripped_text = AllocVec(len + 1, MEMF_ANY);
114 line_size = len + 1;
116 if (line == NULL || stripped_text == NULL)
117 return;
119 /* Make a copy of paragraph text without control sequences
120 * or skip-chars, and with tabs expanded to spaces */
122 for (p = text, q = stripped_text; *p != '\0' && *p != '\n'; )
124 if (*p == '\33')
125 p += 2;
126 else if (*p == '\t')
128 for (i = 0; i < data->tabsize; i++)
129 *q++ = ' ';
130 p++;
132 else if (data->skipchars != NULL)
134 for (r = data->skipchars; *r != '\0' && *r != *p; r++);
135 if (*r == '\0')
136 *q++ = *p;
137 p++;
139 else
140 *q++ = *p++;
142 *q = '\0';
143 stripped_len = MyStrLen(stripped_text);
145 /* Divide this paragraph into lines */
147 line[0] = '\0';
149 while ((stripped_count = FitParagraphLine(stripped_text,
150 stripped_len, width, stripped_pos, window)) != 0)
152 /* Count number of characters for this line in original
153 * text */
155 control_count = tab_count = 0;
156 for (i = 0, p = text + pos; i < stripped_count
157 || (i == stripped_count && *p != ' ' && *p != '\t');
158 p++)
160 if (*p == '\33')
162 control_count += 2;
163 p++;
165 else if (*p == '\t')
167 control_count++;
168 tab_count++;
169 i += data->tabsize;
171 else if (data->skipchars != NULL)
173 for (r = data->skipchars;
174 *r != '\0' && *r != *p; r++);
175 if (*r != '\0' || (*p == ' ' && i == 0))
176 control_count++;
177 else
178 i++;
180 else
181 i++;
183 count =
184 stripped_count + control_count
185 - tab_count * data->tabsize;
187 /* Default to justified text if it's enabled and this
188 * isn't the last line of the paragraph */
190 justify = data->justify;
191 if (justify)
193 /* Count number of spaces in stripped line */
195 p = stripped_text + stripped_pos;
196 for (i = 0, space_count = 0; i < stripped_count; i++)
198 if (p[i] == ' ')
199 space_count++;
201 space_count -= tab_count * data->tabsize;
203 if (space_count == 0)
204 justify = FALSE;
207 if (justify)
209 /* Find out how many extra spaces to insert for fully
210 * justified text */
212 extra_space_count = (width - TextLength(window->RPort,
213 stripped_text + stripped_pos, stripped_count))
214 / space_width;
216 space_multiple = (space_count + extra_space_count)
217 / space_count;
218 bonus_space_count = (space_count + extra_space_count)
219 - space_count * space_multiple;
220 if (bonus_space_count > 0)
221 bonus_space_mod =
222 (space_count + 3) / bonus_space_count;
223 else
224 bonus_space_mod = space_count;
226 /* Don't justify on last line if it will be too
227 * stretched */
229 if (space_multiple > 5
230 && pos + count == len)
231 justify = FALSE;
234 /* Prefix new line with all control characters contained
235 * in previous line */
237 for (p = q = line; *p != '\0'; p++)
239 if (*p == '\33')
241 *q++ = *p++;
242 *q++ = *p;
246 /* Generate line to insert in List object */
248 is_empty = TRUE;
249 p = text + pos;
250 c = p[count];
251 p[count] = '\0';
252 space_no_in = space_no_out = 0;
253 while (*p != '\0')
255 if (*p == ' ' && justify && !is_empty)
257 /* Add extra spaces to justify text */
259 for (i = 0; i < space_multiple; i++)
260 *q++ = ' ';
261 space_no_out += space_multiple;
263 if (bonus_space_count > 0
264 && space_no_in % bonus_space_mod == 0)
266 *q++ = ' ';
267 bonus_space_count--;
269 space_no_out++;
271 p++;
272 space_no_in++;
274 /* Last input space? Make it as wide as
275 * necessary for line to reach right border */
277 if (space_no_in == space_count)
279 while (bonus_space_count-- > 0)
280 *q++ = ' ';
283 else if (*p == '\t')
285 /* Expand tab to spaces */
287 for (i = 0; i < data->tabsize; i++)
288 *q++ = ' ';
289 p++;
291 else if (data->skipchars != NULL)
293 /* Filter out skip-chars */
295 for (r = data->skipchars;
296 *r != '\0' && *r != *p; r++);
297 if (*r == '\0' && !(*p == ' ' && is_empty))
299 *q++ = *p;
300 is_empty = FALSE;
302 p++;
304 else
306 /* No skip-chars, so direct copy */
308 *q++ = *p++;
309 is_empty = FALSE;
312 *q = '\0';
314 /* Add line to list */
316 DoMethod(obj, MUIM_List_InsertSingle, line,
317 MUIV_List_Insert_Bottom);
318 *p = c;
320 /* Move on to first word of next line */
322 stripped_pos += stripped_count;
323 pos += count;
324 if (pos != len)
326 if (data->skipchars != NULL)
328 for (found = FALSE, p = text + pos;
329 !found; pos++, p++)
331 for (r = data->skipchars;
332 *r != '\0' && *r != *p; r++);
333 if (*r == '\0' && *p != ' ' && *p != '\t')
334 found = TRUE;
336 pos--, p--;
338 else
339 for (p = text + pos; *p == ' ' || *p == '\t';
340 pos++, p++);
341 for (p = stripped_text + stripped_pos; *(p++) == ' ';
342 stripped_pos++);
346 else
347 DoMethod(obj, MUIM_List_InsertSingle, "",
348 MUIV_List_Insert_Bottom);
350 if (text[pos] == '\n')
351 pos++;
355 data->typesetting = FALSE;
357 DoMethod(obj, MUIM_List_Redraw, MUIV_List_Redraw_All);
360 IPTR Floattext__OM_NEW(struct IClass *cl, Object *obj, struct opSet *msg)
362 struct Floattext_DATA *data;
363 struct TagItem *tag;
364 struct TagItem *tags;
366 obj = (Object *) DoSuperMethodA(cl, obj, (Msg) msg);
368 if (!obj)
370 return 0;
373 data = INST_DATA(cl, obj);
375 SetAttrs(obj, MUIA_List_ConstructHook, MUIV_List_ConstructHook_String,
376 MUIA_List_DestructHook, MUIV_List_DestructHook_String, TAG_DONE);
378 for (tags = msg->ops_AttrList; (tag = NextTagItem(&tags));)
380 switch (tag->ti_Tag)
382 case MUIA_Floattext_Justify:
383 data->justify = tag->ti_Data;
384 break;
386 case MUIA_Floattext_SkipChars:
387 data->skipchars = (STRPTR) tag->ti_Data;
388 break;
390 case MUIA_Floattext_TabSize:
391 data->tabsize = tag->ti_Data;
392 break;
394 case MUIA_Floattext_Text:
395 data->text = StrDup((STRPTR) tag->ti_Data);
396 break;
400 if (data->tabsize == 0)
401 data->tabsize = 8;
402 else if (data->tabsize > 20)
403 data->tabsize = 20;
405 SetText(obj, data);
407 return (IPTR) obj;
410 IPTR Floattext__OM_DISPOSE(struct IClass *cl, Object *obj,
411 struct opSet *msg)
413 struct Floattext_DATA *data = INST_DATA(cl, obj);
415 FreeVec(data->text);
417 return DoSuperMethodA(cl, obj, (Msg) msg);
420 IPTR Floattext__OM_GET(struct IClass *cl, Object *obj, struct opGet *msg)
422 struct Floattext_DATA *data = INST_DATA(cl, obj);
423 #define STORE *(msg->opg_Storage)
425 switch (msg->opg_AttrID)
427 case MUIA_Floattext_Justify:
428 STORE = data->justify;
429 return 1;
431 case MUIA_Floattext_Text:
432 STORE = (IPTR) data->text;
433 return 1;
437 #undef STORE
439 return DoSuperMethodA(cl, obj, (Msg) msg);
442 IPTR Floattext__OM_SET(struct IClass *cl, Object *obj, struct opSet *msg)
444 struct Floattext_DATA *data = INST_DATA(cl, obj);
445 struct TagItem *tag;
446 struct TagItem *tags;
447 BOOL changed = FALSE;
449 for (tags = msg->ops_AttrList; (tag = NextTagItem(&tags));)
451 switch (tag->ti_Tag)
453 case MUIA_Floattext_Justify:
454 data->justify = tag->ti_Data != 0;
455 changed = TRUE;
456 break;
458 case MUIA_Floattext_SkipChars:
459 data->skipchars = (STRPTR) tag->ti_Data;
460 changed = TRUE;
461 break;
463 case MUIA_Floattext_TabSize:
464 data->tabsize = tag->ti_Data;
465 changed = TRUE;
466 break;
468 case MUIA_Floattext_Text:
469 FreeVec(data->text);
470 data->text = StrDup((STRPTR) tag->ti_Data);
471 changed = TRUE;
472 break;
477 if (changed) // To avoid recursion
479 if (data->tabsize == 0)
480 data->tabsize = 8;
481 else if (data->tabsize > 20)
482 data->tabsize = 20;
484 SetText(obj, data);
487 return DoSuperMethodA(cl, obj, (Msg) msg);
490 /**************************************************************************
491 MUIM_Draw
492 **************************************************************************/
493 IPTR Floattext__MUIM_Draw(struct IClass *cl, Object *obj,
494 struct MUIP_Draw *msg)
496 struct Floattext_DATA *data = INST_DATA(cl, obj);
498 if (!data->typesetting)
499 DoSuperMethodA(cl, obj, (Msg) msg);
501 if ((msg->flags & MADF_DRAWOBJECT) != 0 && data->oldwidth != _width(obj))
503 data->oldwidth = _width(obj);
504 SetText(obj, data);
507 return 0;
510 #if ZUNE_BUILTIN_FLOATTEXT
511 BOOPSI_DISPATCHER(IPTR, Floattext_Dispatcher, cl, obj, msg)
513 switch (msg->MethodID)
515 case OM_NEW:
516 return Floattext__OM_NEW(cl, obj, msg);
517 case OM_DISPOSE:
518 return Floattext__OM_DISPOSE(cl, obj, msg);
519 case OM_GET:
520 return Floattext__OM_GET(cl, obj, msg);
521 case OM_SET:
522 return Floattext__OM_SET(cl, obj, msg);
523 case MUIM_Draw:
524 return Floattext__MUIM_Draw(cl, obj, (struct MUIP_Draw *)msg);
526 default:
527 return DoSuperMethodA(cl, obj, msg);
530 BOOPSI_DISPATCHER_END
532 const struct __MUIBuiltinClass _MUI_Floattext_desc =
534 MUIC_Floattext,
535 MUIC_List,
536 sizeof(struct Floattext_DATA),
537 (void *) Floattext_Dispatcher
539 #endif /* ZUNE_BUILTIN_FLOATTEXT */