1 /***************************************************************************
3 TextEditor.mcc - Textediting MUI Custom Class
4 Copyright (C) 1997-2000 Allan Odgaard
5 Copyright (C) 2005-2014 TextEditor.mcc Open Source Team
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 TextEditor class Support Site: http://www.sf.net/projects/texteditor-mcc
21 ***************************************************************************/
25 #include <proto/dos.h>
26 #include <proto/exec.h>
31 /*************************************************************************/
35 /*************************************************************************/
37 static LONG
GetHex(const char *src
)
43 if((src
[0] >= '0' && src
[0] <= '9'))
44 result
= src
[0] - '0';
45 else if((src
[0] >= 'a' && src
[0] <= 'f'))
46 result
= src
[0] - 'a' + 10;
47 else if((src
[0] >= 'A' && src
[0] <= 'F'))
48 result
= src
[0] - 'A' + 10;
56 /************************************************************************
57 Convert a =XX string to its value (into *val). Returns TRUE if
58 conversion was successful; in that case *src_ptr will be advanced as well.
59 *************************************************************************/
60 static BOOL
GetQP(const char **src_ptr
, unsigned char *val
)
63 const char *src
= *src_ptr
;
69 if((rc
= GetHex(src
)) != -1)
73 if((rc
= GetHex(src
)) != -1)
89 /************************************************************************
90 Reads out the next value at *src_ptr and advances src_ptr.
91 Returns TRUE if succedded else FALSE
92 *************************************************************************/
93 static BOOL
GetLong(const char **src_ptr
, LONG
*val
)
100 if((chars
= StrToLong(*src_ptr
, val
)) != -1)
113 /************************************************************************
114 Returns the end of line in the current line (pointing at the linefeed).
115 If a 0 byte is encountered it returns the pointer to the 0 byte.
117 This function also counts the number of tabs within this line.
118 *************************************************************************/
119 static const char *FindEOL(const char *src
, int *tabs_ptr
)
123 const char *eol
= src
;
127 while((c
= *eol
) != '\0')
131 else if(c
== '\r' || c
== '\n')
146 /************************************************************************
147 searches through a string and returns TRUE if the string contains
148 any text (except newlines) until the stopchar is found
149 *************************************************************************/
150 static BOOL
ContainsText(const char *str
, const char stopchar
)
152 BOOL contains
= FALSE
;
158 while(contains
== FALSE
&& *str
>= ' ')
162 else if(*str
> ' ') // greater than 0x20 (space) == readable text
174 /// PlainImportHookFunc()
175 /************************************************************************
176 The plain import hook. It supports following escape sequences:
178 <ESC> + u Set the soft style to underline.
179 <ESC> + b Set the soft style to bold.
180 <ESC> + i Set the soft style to italic.
181 <ESC> + n Set the soft style back to normal.
182 <ESC> + h Highlight the current line.
183 <ESC> + p[x] Change to color x, where x is taken from the colormap.
184 0 means normal. The color is reset for each new line.
187 The following sequences are only valid at the beginning of a line.
189 <ESC> + l Left justify current and following lines.
190 <ESC> + r Right justify current and following lines.
191 <ESC> + c Center current and following lines.
192 <ESC> + [s:x] Create a separator. x is a bit combination of flags:
193 Placement (mutually exclusive):
198 8 = StrikeThru - Draw separator ontop of text.
199 16 = Thick - Make separator extra thick.
201 Note: Tabs are converted to the number of spaces specified in the
204 *************************************************************************/
205 HOOKPROTONHNO(PlainImportHookFunc
, STRPTR
, struct ImportMessage
*msg
)
207 STRPTR result
= NULL
;
209 const char *src
= msg
->Data
;
214 // check for a valid TAB size
215 if(msg
->TabSize
<= 0)
217 E(DBF_IMPORT
, "invalid TAB size %ld", msg
->TabSize
);
218 // assume the default TAB size to avoid a division by zero
222 if((eol
= FindEOL(src
, msg
->ConvertTabs
== FALSE
? NULL
: &tabs
)) != NULL
)
225 struct LineNode
*line
= msg
->linenode
;
226 LONG wrap
= msg
->ImportWrap
;
227 ULONG allocatedContents
;
229 len
= eol
- src
+ (msg
->TabSize
* tabs
);
231 // allocate memory for the contents plus the trailing LF and NUL bytes
232 allocatedContents
= len
+2;
233 if((line
->Contents
= AllocVecPooled(msg
->PoolHandle
, allocatedContents
)) != NULL
)
235 unsigned char *dest_start
= (unsigned char *)line
->Contents
;
236 unsigned char *dest
= dest_start
;
237 unsigned char *dest_word_start
= dest_start
;
238 unsigned char *src_word_start
= (unsigned char *)src
;
240 /* Style and color state */
243 struct Grow style_grow
;
244 struct Grow color_grow
;
246 struct LineStyle newStyle
;
247 struct LineColor newColor
;
249 InitGrow(&style_grow
, msg
->PoolHandle
, sizeof(newStyle
));
250 InitGrow(&color_grow
, msg
->PoolHandle
, sizeof(newColor
));
252 // remember the allocation size
253 line
->allocatedContents
= allocatedContents
;
258 unsigned char c
= *src
++;
260 if(c
== '\t' && msg
->ConvertTabs
== TRUE
)
264 for(i
=(dest
- dest_start
) % msg
->TabSize
; i
< msg
->TabSize
; i
++)
269 else if(c
== '\033') // ESC sequence
276 if(isFlagClear(state
, BOLD
))
278 newStyle
.column
= dest
- dest_start
+ 1;
279 newStyle
.style
= BOLD
;
280 D(DBF_IMPORT
, "adding bold style at column %ld", newStyle
.column
);
281 AddToGrow(&style_grow
, &newStyle
);
282 setFlag(state
, BOLD
);
289 if(isFlagClear(state
, ITALIC
))
291 newStyle
.column
= dest
- dest_start
+ 1;
292 newStyle
.style
= ITALIC
;
293 D(DBF_IMPORT
, "adding italic style at column %ld", newStyle
.column
);
294 AddToGrow(&style_grow
, &newStyle
);
295 setFlag(state
, ITALIC
);
302 if(isFlagClear(state
, UNDERLINE
))
304 newStyle
.column
= dest
- dest_start
+ 1;
305 newStyle
.style
= UNDERLINE
;
306 D(DBF_IMPORT
, "adding underline style at column %ld", newStyle
.column
);
307 AddToGrow(&style_grow
, &newStyle
);
308 setFlag(state
, UNDERLINE
);
315 line
->Highlight
= TRUE
;
321 if(isFlagSet(state
, BOLD
))
323 newStyle
.column
= dest
- dest_start
+ 1;
324 newStyle
.style
= ~BOLD
;
325 D(DBF_IMPORT
, "removing bold style at column %ld", newStyle
.column
);
326 AddToGrow(&style_grow
, &newStyle
);
327 clearFlag(state
, BOLD
);
329 if(isFlagSet(state
, ITALIC
))
331 newStyle
.column
= dest
- dest_start
+ 1;
332 newStyle
.style
= ~ITALIC
;
333 D(DBF_IMPORT
, "removing italic style at column %ld", newStyle
.column
);
334 AddToGrow(&style_grow
, &newStyle
);
335 clearFlag(state
, ITALIC
);
337 if(isFlagSet(state
, UNDERLINE
))
339 newStyle
.column
= dest
- dest_start
+ 1;
340 newStyle
.style
= ~UNDERLINE
;
341 D(DBF_IMPORT
, "removing italic style at column %ld", newStyle
.column
);
342 AddToGrow(&style_grow
, &newStyle
);
343 clearFlag(state
, UNDERLINE
);
350 line
->Flow
= MUIV_TextEditor_Flow_Left
;
351 D(DBF_IMPORT
, "left aligned text flow");
357 line
->Flow
= MUIV_TextEditor_Flow_Center
;
358 D(DBF_IMPORT
, "centered flow");
364 line
->Flow
= MUIV_TextEditor_Flow_Right
;
365 D(DBF_IMPORT
, "right aligned text flow");
377 if(GetLong(&src
, &pen
) == TRUE
)
381 newColor
.column
= dest
- dest_start
+ 1;
382 newColor
.color
= pen
;
383 D(DBF_IMPORT
, "adding color %ld at column %ld", newColor
.color
, newColor
.column
);
384 AddToGrow(&color_grow
, &newColor
);
389 setFlag(state
, COLOURED
);
409 if(GetLong(&src
, &flags
) == TRUE
)
413 line
->Separator
= flags
;
428 // src is already advanced
429 src_word_start
= (unsigned char *)src
;
430 dest_word_start
= dest
;
433 if(wrap
!= 0 && dest
- dest_start
>= wrap
)
435 /* Only leave the loop, if we really have added some characters
436 * (at least one word) to the line */
437 if(dest_word_start
!= dest_start
)
439 /* src points to the real word start, but we add one when we return eol */
440 eol
= (char *)(src_word_start
- 1);
441 dest
= dest_word_start
;
447 } /* while (src < eol) */
449 // terminate the color array, but only if there are any colors at all
450 if(color_grow
.itemCount
> 0)
452 // ensure that we terminate the clip with color 0
453 if(isFlagSet(state
, COLOURED
))
455 D(DBF_IMPORT
, "removing color as termination");
456 newColor
.column
= strlen(line
->Contents
)+1;
458 AddToGrow(&color_grow
, &newColor
);
461 newColor
.column
= EOC
;
463 AddToGrow(&color_grow
, &newColor
);
466 D(DBF_IMPORT
, "added %ld color sections", color_grow
.itemCount
);
467 line
->Colors
= (struct LineColor
*)color_grow
.array
;
469 // terminate the style array, but only if there are any styles at all
470 if(style_grow
.itemCount
> 0)
472 LONG lastColumn
= strlen(line
->Contents
)+1;
474 // ensure that we terminate the clip with plain style
475 if(isFlagSet(state
, BOLD
))
477 D(DBF_IMPORT
, "removing bold style as termination");
478 newStyle
.column
= lastColumn
;
479 newStyle
.style
= ~BOLD
;
480 AddToGrow(&style_grow
, &newStyle
);
482 if(isFlagSet(state
, ITALIC
))
484 D(DBF_IMPORT
, "removing italic style as termination");
485 newStyle
.column
= lastColumn
;
486 newStyle
.style
= ~ITALIC
;
487 AddToGrow(&style_grow
, &newStyle
);
489 if(isFlagSet(state
, UNDERLINE
))
491 D(DBF_IMPORT
, "removing underline style as termination");
492 newStyle
.column
= lastColumn
;
493 newStyle
.style
= ~UNDERLINE
;
494 AddToGrow(&style_grow
, &newStyle
);
497 newStyle
.column
= EOS
;
499 AddToGrow(&style_grow
, &newStyle
);
502 D(DBF_IMPORT
, "added %ld style sections", style_grow
.itemCount
);
503 line
->Styles
= (struct LineStyle
*)style_grow
.array
;
508 line
->Length
= dest
- dest_start
; /* this excludes \n */
511 if(eol
!= NULL
&& eol
[0] != '\0')
514 result
= (STRPTR
)eol
;
521 MakeHook(ImPlainHook
, PlainImportHookFunc
);
525 /************************************************************************
526 The MIME import hook. It supports following escape sequences:
528 <ESC> + u Set the soft style to underline.
529 <ESC> + b Set the soft style to bold.
530 <ESC> + i Set the soft style to italic.
531 <ESC> + n Set the soft style back to normal.
532 <ESC> + h Highlight the current line.
533 <ESC> + p[x] Change to color x, where x is taken from the colormap.
534 0 means normal. The color is reset for each new line.
537 The following sequences are only valid at the beginning of a line.
539 <ESC> + l Left justify current and following lines.
540 <ESC> + r Right justify current and following lines.
541 <ESC> + c Center current and following lines.
542 <ESC> + [s:x] Create a separator. x is a bit combination of flags:
543 Placement (mutually exclusive):
548 8 = StrikeThru - Draw separator ontop of text.
549 16 = Thick - Make separator extra thick.
551 Note: Tabs are converted to the number of spaces specified in the
554 *************************************************************************/
555 static STRPTR
MimeImport(struct ImportMessage
*msg
, LONG type
)
557 STRPTR result
= NULL
;
559 const char *src
= msg
->Data
;
564 if((eol
= FindEOL(src
, msg
->ConvertTabs
== FALSE
? NULL
: &tabs
)) != NULL
)
567 struct LineNode
*line
= msg
->linenode
;
568 LONG wrap
= msg
->ImportWrap
;
569 ULONG allocatedContents
;
571 len
= eol
- src
+ (msg
->TabSize
* tabs
);
573 // allocate some more memory for the possible quote mark '>', note that if
574 // a '=' is detected at the end of a line this memory is not sufficient!
575 allocatedContents
= len
+4;
576 if((line
->Contents
= AllocVecPooled(msg
->PoolHandle
, allocatedContents
)) != NULL
)
578 BOOL lastWasSeparator
= TRUE
;
579 unsigned char *dest_start
= (unsigned char *)line
->Contents
;
580 unsigned char *dest
= dest_start
;
581 unsigned char *dest_word_start
= dest_start
;
582 unsigned char *src_word_start
= (unsigned char *)src
;
584 /* Style and color state */
589 struct Grow style_grow
;
590 struct Grow color_grow
;
592 struct LineStyle newStyle
;
593 struct LineColor newColor
;
595 InitGrow(&style_grow
, msg
->PoolHandle
, sizeof(newStyle
));
596 InitGrow(&color_grow
, msg
->PoolHandle
, sizeof(newColor
));
598 // remember the allocation size
599 line
->allocatedContents
= allocatedContents
;
602 line
->Highlight
= TRUE
;
603 else if(src
[0] == '<')
605 if(src
[1] == 's' && src
[2] == 'b' && src
[3] == '>')
607 line
->Separator
= LNSF_Middle
;
609 line
->Flow
= MUIV_TextEditor_Flow_Center
;
610 line
->clearFlow
= TRUE
;
612 else if(src
[1] == 't' && src
[2] == 's' && src
[3] == 'b' && src
[4] == '>')
615 line
->Separator
= LNSF_Thick
|LNSF_Middle
;
616 line
->Flow
= MUIV_TextEditor_Flow_Center
;
617 line
->clearFlow
= TRUE
;
624 line
->Highlight
= TRUE
;
630 unsigned char c
= *src
++;
634 lastWasSeparator
= TRUE
;
636 else if(c
== '\t' && msg
->ConvertTabs
== TRUE
)
640 for(i
=(dest
- dest_start
) % msg
->TabSize
; i
< msg
->TabSize
; i
++)
643 lastWasSeparator
= TRUE
;
651 if(shownext
& ITALIC
)
653 else if(isFlagSet(state
, ITALIC
) || (lastWasSeparator
== TRUE
&& ContainsText(src
, '/') == TRUE
))
655 newStyle
.column
= dest
- dest_start
+ 1;
656 newStyle
.style
= isFlagSet(state
, ITALIC
) ? ~ITALIC
: ITALIC
;
657 AddToGrow(&style_grow
, &newStyle
);
660 lastWasSeparator
= TRUE
;
667 lastWasSeparator
= TRUE
;
675 else if(isFlagSet(state
, BOLD
) || (lastWasSeparator
== TRUE
&& ContainsText(src
, '*') == TRUE
))
677 newStyle
.column
= dest
- dest_start
+ 1;
678 newStyle
.style
= isFlagSet(state
, BOLD
) ? ~BOLD
: BOLD
;
679 AddToGrow(&style_grow
, &newStyle
);
682 lastWasSeparator
= TRUE
;
689 lastWasSeparator
= TRUE
;
695 if(shownext
& UNDERLINE
)
696 shownext
^= UNDERLINE
;
697 else if(isFlagSet(state
, UNDERLINE
) || (lastWasSeparator
== TRUE
&& ContainsText(src
, '_') == TRUE
))
699 newStyle
.column
= dest
- dest_start
+ 1;
700 newStyle
.style
= isFlagSet(state
, UNDERLINE
) ? ~UNDERLINE
: UNDERLINE
;
701 AddToGrow(&style_grow
, &newStyle
);
704 lastWasSeparator
= TRUE
;
708 shownext
|= UNDERLINE
;
711 lastWasSeparator
= TRUE
;
717 if(shownext
& COLOURED
)
718 shownext
^= COLOURED
;
719 else if(isFlagSet(state
, COLOURED
) || (lastWasSeparator
== TRUE
&& ContainsText(src
, '#') == TRUE
))
721 newColor
.column
= dest
- dest_start
+ 1;
722 newColor
.color
= isFlagSet(state
, COLOURED
) ? 0 : 7;
723 AddToGrow(&style_grow
, &newStyle
);
726 lastWasSeparator
= TRUE
;
730 shownext
|= COLOURED
;
733 lastWasSeparator
= TRUE
;
737 // This is a concatenated line
738 if(type
> 0 && GetQP(&src
, &c
) == FALSE
)
748 unsigned char *new_dest_start
;
752 if((eol
= FindEOL(src
, msg
->ConvertTabs
== FALSE
? NULL
: &tabs
)) == NULL
)
755 /* The size of the dest buffer has to be increased now */
756 len
+= eol
- src
+ (msg
->TabSize
* tabs
);
758 if((new_dest_start
= (unsigned char*)AllocVecPooled(msg
->PoolHandle
, len
+ 4)) == NULL
)
761 memcpy(new_dest_start
, dest_start
, dest
- dest_start
);
762 FreeVecPooled(msg
->PoolHandle
,dest_start
);
764 /* Update all dest variables */
765 dest_word_start
= new_dest_start
+ (dest_word_start
- dest_start
);
766 dest
= new_dest_start
+ (dest
- dest_start
);
767 line
->Contents
= (char *)new_dest_start
;
768 dest_start
= (unsigned char *)line
->Contents
;
770 lastWasSeparator
= FALSE
;
775 else if(c
== '\033') // like the plain import hook we manage ESC sequences as well
781 newStyle
.column
= dest
- dest_start
+ 1;
782 newStyle
.style
= BOLD
;
783 AddToGrow(&style_grow
, &newStyle
);
784 setFlag(escstate
, BOLD
);
790 newStyle
.column
= dest
- dest_start
+ 1;
791 newStyle
.style
= ITALIC
;
792 AddToGrow(&style_grow
, &newStyle
);
793 setFlag(escstate
, ITALIC
);
799 newStyle
.column
= dest
- dest_start
+ 1;
800 newStyle
.style
= UNDERLINE
;
801 AddToGrow(&style_grow
, &newStyle
);
802 setFlag(escstate
, UNDERLINE
);
808 line
->Highlight
= TRUE
;
814 if(isFlagSet(state
, BOLD
))
816 newStyle
.column
= dest
- dest_start
+ 1;
817 newStyle
.style
= ~BOLD
;
818 AddToGrow(&style_grow
, &newStyle
);
819 clearFlag(state
, BOLD
);
820 clearFlag(escstate
, BOLD
);
822 if(isFlagSet(state
, ITALIC
))
824 newStyle
.column
= dest
- dest_start
+ 1;
825 newStyle
.style
= ~ITALIC
;
826 AddToGrow(&style_grow
, &newStyle
);
827 clearFlag(state
, ITALIC
);
828 clearFlag(escstate
, ITALIC
);
830 if(isFlagSet(state
, UNDERLINE
))
832 newStyle
.column
= dest
- dest_start
+ 1;
833 newStyle
.style
= ~UNDERLINE
;
834 AddToGrow(&style_grow
, &newStyle
);
835 clearFlag(state
, UNDERLINE
);
836 clearFlag(escstate
, UNDERLINE
);
843 line
->Flow
= MUIV_TextEditor_Flow_Left
;
849 line
->Flow
= MUIV_TextEditor_Flow_Center
;
855 line
->Flow
= MUIV_TextEditor_Flow_Right
;
867 if(GetLong(&src
, &pen
) == TRUE
)
871 newColor
.column
= dest
- dest_start
+ 1;
872 newColor
.color
= pen
;
873 AddToGrow(&color_grow
, &newColor
);
876 escstate
^= COLOURED
;
878 escstate
|= COLOURED
;
898 if(GetLong(&src
, &flags
) == TRUE
)
902 line
->Separator
= flags
;
912 lastWasSeparator
= FALSE
;
918 /* src is already advanced */
919 src_word_start
= (unsigned char *)src
;
920 dest_word_start
= dest
;
922 lastWasSeparator
= TRUE
;
926 lastWasSeparator
= TRUE
;
930 lastWasSeparator
= FALSE
;
933 if(wrap
!= 0 && dest
- dest_start
>= wrap
)
935 /* Only leave the loop, if we really have added some characters
936 * (at least one word) to the line */
937 if(dest_word_start
!= dest_start
)
939 /* src points to the real word start, but we add one when we return eol */
940 eol
= (char *)(src_word_start
- 1);
941 dest
= dest_word_start
;
947 } /* while (src < eol) */
949 // terminate the color array, but only if there are any colors at all
950 if(color_grow
.itemCount
> 0)
952 newColor
.column
= EOC
;
954 AddToGrow(&color_grow
, &newColor
);
957 line
->Colors
= (struct LineColor
*)color_grow
.array
;
959 // terminate the style array, but only if there are any styles at all
960 if(style_grow
.itemCount
> 0)
962 newStyle
.column
= EOS
;
964 AddToGrow(&style_grow
, &newStyle
);
967 line
->Styles
= (struct LineStyle
*)style_grow
.array
;
972 line
->Length
= dest
- dest_start
; /* this excludes \n */
975 if(eol
!= NULL
&& eol
[0] != '\0')
978 result
= (STRPTR
)eol
;
987 /// EMailImportHookFunc()
988 HOOKPROTONHNO(EMailImportHookFunc
, STRPTR
, struct ImportMessage
*msg
)
990 return MimeImport(msg
, 0);
992 MakeHook(ImEMailHook
, EMailImportHookFunc
);
995 /// MIMEImportHookFunc()
996 HOOKPROTONHNO(MIMEImportHookFunc
, STRPTR
, struct ImportMessage
*msg
)
998 return MimeImport(msg
, 1);
1000 MakeHook(ImMIMEHook
, MIMEImportHookFunc
);
1003 /// MIMEQuoteImportHookFunc()
1004 HOOKPROTONHNO(MIMEQuoteImportHookFunc
, STRPTR
, struct ImportMessage
*msg
)
1006 return MimeImport(msg
, 2);
1008 MakeHook(ImMIMEQuoteHook
, MIMEQuoteImportHookFunc
);