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 <graphics/gfxmacros.h>
26 #include <graphics/text.h>
27 #include <libraries/mui.h>
28 #include <clib/alib_protos.h>
29 #include <proto/graphics.h>
30 #include <proto/layers.h>
31 #include <proto/exec.h>
32 #include <proto/intuition.h>
34 #include <proto/muimaster.h>
36 /*************************************************************************/
42 void AddClipping(struct InstData
*data
)
46 if(data
->clipcount
== 0)
48 data
->cliphandle
= MUI_AddClipping(muiRenderInfo(data
->object
), _mleft(data
->object
), _mtop(data
->object
), _mwidth(data
->object
), _mheight(data
->object
));
57 void RemoveClipping(struct InstData
*data
)
62 if(data
->clipcount
== 0)
64 MUI_RemoveClipping(muiRenderInfo(data
->object
), data
->cliphandle
);
72 void FreeTextMem(struct InstData
*data
, struct MinList
*lines
)
74 struct line_node
*line
;
78 while((line
= RemFirstLine(lines
)) != NULL
)
80 FreeVecPooled(data
->mypool
, line
->line
.Contents
);
81 if(line
->line
.Styles
!= NULL
)
82 FreeVecPooled(data
->mypool
, line
->line
.Styles
);
83 if(line
->line
.Colors
!= NULL
)
84 FreeVecPooled(data
->mypool
, line
->line
.Colors
);
86 FreeVecPooled(data
->mypool
, line
);
96 /*-----------------------------------*
97 * Initializes a line_node structure *
98 *-----------------------------------*/
99 BOOL
Init_LineNode(struct InstData
*data
, struct line_node
*line
, CONST_STRPTR text
)
101 BOOL success
= FALSE
;
110 while(*p
!= '\n' && *p
!= '\0')
115 // count one byte more for the trailing LF byte
118 // and allocate yet another additional byte for the trailing NUL byte
119 if((ctext
= AllocVecPooled(data
->mypool
, textlength
+1)) != NULL
)
121 strlcpy(ctext
, text
, textlength
+1);
123 line
->line
.Contents
= ctext
;
124 line
->line
.Length
= textlength
;
125 line
->line
.allocatedContents
= textlength
+1;
126 if(data
->rport
!= NULL
)
127 line
->visual
= VisualHeight(data
, line
);
128 line
->line
.Highlight
= FALSE
;
129 line
->line
.Styles
= NULL
;
130 line
->line
.Colors
= NULL
;
131 line
->line
.Flow
= MUIV_TextEditor_Flow_Left
;
132 line
->line
.Separator
= LNSF_None
;
143 BOOL
ExpandLine(struct InstData
*data
, struct line_node
*line
, LONG length
)
151 if(line
->line
.allocatedContents
>= 2 && line
->line
.Length
>= line
->line
.allocatedContents
)
153 E(DBF_STYLE
, "line length (%ld) > allocated size (%ld)", line
->line
.Length
, line
->line
.allocatedContents
-1);
156 expandedSize
= line
->line
.allocatedContents
+40+length
;
158 if((newbuffer
= AllocVecPooled(data
->mypool
, expandedSize
)) != NULL
)
160 strlcpy(newbuffer
, line
->line
.Contents
, expandedSize
);
161 FreeVecPooled(data
->mypool
, line
->line
.Contents
);
162 line
->line
.Contents
= newbuffer
;
163 line
->line
.allocatedContents
= expandedSize
;
173 BOOL
CompressLine(struct InstData
*data
, struct line_node
*line
)
180 compressedSize
= strlen(line
->line
.Contents
)+1;
181 if(compressedSize
< line
->line
.allocatedContents
)
185 if((newbuffer
= AllocVecPooled(data
->mypool
, compressedSize
)) != NULL
)
187 strlcpy(newbuffer
, line
->line
.Contents
, compressedSize
);
188 FreeVecPooled(data
->mypool
, line
->line
.Contents
);
189 line
->line
.Contents
= newbuffer
;
190 line
->line
.Length
= strlen(newbuffer
);
191 line
->line
.allocatedContents
= compressedSize
;
197 // no compression necessary
207 void InsertLines(struct MinList
*lines
, struct line_node
*after
)
209 struct line_node
*line
;
213 // insert all lines backwards after the given line
214 // this will effectively add all lines in exactly the same order
215 while((line
= RemLastLine(lines
)) != NULL
)
217 InsertLine(line
, after
);
225 /*-----------------------------------------------------*
226 * Returns the number of chars that will fit on a line *
227 *-----------------------------------------------------*/
228 LONG
LineCharsWidth(struct InstData
*data
, CONST_STRPTR text
)
232 LONG width
= _mwidth(data
->object
);
236 textlen
= text
!= NULL
? strlen(text
)-1 : 0; // the last char is always a "\n"
238 // check the innerwidth as well. But we also check if we need to
239 // take care of any of the word wrapping techniques we provide
240 if(width
> 0 && textlen
> 0 && data
->WrapMode
!= MUIV_TextEditor_WrapMode_NoWrap
)
242 struct TextExtent tExtend
;
243 ULONG fontheight
= data
->font
? data
->font
->tf_YSize
: 0;
245 // see how many chars of our text fit to the current innerwidth of the
247 c
= TextFitNew(&data
->tmprp
, text
, textlen
, &tExtend
, NULL
, 1, width
, fontheight
, data
->TabSizePixels
);
250 // if all text fits, then we have to do the calculations once more and
251 // see if also the ending cursor might fit on the line
252 width
-= (data
->CursorWidth
== 6) ? TextLength(&data
->tmprp
, " ", 1) : data
->CursorWidth
;
253 c
= TextFitNew(&data
->tmprp
, text
, textlen
, &tExtend
, NULL
, 1, width
, fontheight
, data
->TabSizePixels
);
256 // if the user selected soft wrapping with a defined wrapborder
257 // we have to check if we take that border or do the soft wrapping
258 // at the innerwidth of the texteditor
259 if(data
->WrapBorder
> 0 && c
> data
->WrapBorder
&& data
->WrapMode
== MUIV_TextEditor_WrapMode_SoftWrap
)
260 c
= data
->WrapBorder
;
262 // now we check whether all chars fit on the current innerwidth
263 // or if we have to do soft word wrapping by searching for the last
264 // occurance of a linear white space character
271 // search backwards for a linear whitespace (LWSP)
272 while(tc
>= 0 && text
[tc
] != ' ' && text
[tc
] != '\t')
281 // otherwise always +1 because an ending line should always contain the cursor
294 /*-------------------------------------------------------*
295 * Return the number of visual lines that the line fills *
296 *-------------------------------------------------------*/
297 ULONG
VisualHeight(struct InstData
*data
, struct line_node
*line
)
303 if(isFlagSet(data
->flags
, FLG_HScroll
))
308 ULONG length
= strlen(line
->line
.Contents
);
312 LONG d
= LineCharsWidth(data
, &line
->line
.Contents
[c
]);
329 /*-----------------------------------------*
330 * Convert an xoffset to a number of lines *
331 *-----------------------------------------*/
332 void OffsetToLines(struct InstData
*data
, LONG x
, struct line_node
*line
, struct pos_info
*pos
)
336 if(data
->shown
== TRUE
)
344 LONG e
= LineCharsWidth(data
, &line
->line
.Contents
[c
]);
367 pos
->extra
= line
->line
.Length
-1;
375 LONG
LineNr(struct InstData
*data
, struct line_node
*line
)
378 struct line_node
*actual
= GetFirstLine(&data
->linelist
);
382 while(line
!= actual
)
385 actual
= GetNextLine(actual
);
394 struct line_node
*LineNode(struct InstData
*data
, LONG linenr
)
396 struct line_node
*actual
= GetFirstLine(&data
->linelist
);
397 struct line_node
*next
;
401 while(--linenr
&& (next
= GetNextLine(actual
)) != NULL
)
412 /*------------------*
414 *------------------*/
415 void SetCursor(struct InstData
*data
, LONG x
, struct line_node
*line
, BOOL Set
)
417 unsigned char chars
[4] = " \0";
424 BOOL clipping
= FALSE
;
425 struct RastPort
*rp
= data
->rport
;
427 UWORD styles
[3] = {0, 0, 0};
428 UWORD colors
[3] = {0, 0, 0};
437 data
->update
== FALSE
||
438 (data
->scrollaction
== TRUE
&& Set
== TRUE
) ||
439 data
->ypos
!= _mtop(data
->object
) ||
440 data
->shown
== FALSE
||
441 isFlagSet(data
->flags
, FLG_ReadOnly
) ||
442 isFlagSet(data
->flags
, FLG_Quiet
) ||
443 isFlagSet(data
->flags
, FLG_Ghosted
))
445 data
->cursor_shown
= FALSE
;
451 line_nr
= LineToVisual(data
, line
) - 1;
452 OffsetToLines(data
, x
, line
, &pos
);
454 if(line_nr
+ pos
.lines
<= data
->maxlines
&& line_nr
+ pos
.lines
> 0)
456 for(c
= -1; c
< 2; c
++)
458 if(x
+c
>= pos
.bytes
&& x
+c
< pos
.extra
)
465 styles
[c
+1] = GetStyle(x
+c
, line
);
466 colors
[c
+1] = GetColor(x
+c
, line
);
467 chars
[c
+1] = (line
->line
.Contents
[x
+c
] != '\n') ? line
->line
.Contents
[x
+c
] : ' ';
471 // calculate the cursor width
472 // if it is set to 6 then we should find out how the width of the current char is
473 if(data
->CursorWidth
== 6)
474 cursor_width
= TextLengthNew(&data
->tmprp
, (chars
[1] < ' ') ? (char *)" " : (char *)&chars
[1], 1, data
->TabSizePixels
);
476 cursor_width
= data
->CursorWidth
;
478 xplace
= _mleft(data
->object
) + TextLengthNew(&data
->tmprp
, &line
->line
.Contents
[x
-pos
.x
], pos
.x
+start
, data
->TabSizePixels
);
479 flow
= FlowSpace(data
, line
->line
.Flow
, &line
->line
.Contents
[pos
.bytes
]);
481 yplace
= data
->ypos
+ (data
->fontheight
* (line_nr
+ pos
.lines
- 1));
482 cursorxplace
= xplace
+ TextLengthNew(&data
->tmprp
, &line
->line
.Contents
[x
+start
], 0-start
, data
->TabSizePixels
);
484 //D(DBF_STARTUP, "xplace: %ld, yplace: %ld cplace: %ld, innerwidth: %ld width: %ld %ld", xplace, yplace, cursorxplace, _mwidth(data->object), _width(data->object), _mleft(data->object));
486 if(xplace
<= _mright(data
->object
))
488 // clear area near the cursor first
489 DoMethod(data
->object
, MUIM_DrawBackground
, xplace
, yplace
,
490 TextLengthNew(&data
->tmprp
, start
== 0 ? (STRPTR
)chars
+1 : (STRPTR
)chars
, stop
-start
+1, data
->TabSizePixels
),
492 cursorxplace
- (isFlagSet(data
->flags
, FLG_InVGrp
) ? _mleft(data
->object
) : 0),
493 (isFlagSet(data
->flags
, FLG_InVGrp
) ? 0 : _mtop(data
->object
)) + data
->fontheight
*(data
->visual_y
+line_nr
+pos
.lines
-2),
497 (data
->inactiveCursor
== TRUE
&& isFlagClear(data
->flags
, FLG_Active
) && isFlagClear(data
->flags
, FLG_Activated
)))
499 SetAPen(rp
, MUIPEN(data
->cursorcolor
));
502 // if the gadget is in inactive state we just draw a skeleton cursor instead
503 if(data
->inactiveCursor
== TRUE
&& isFlagClear(data
->flags
, FLG_Active
) && isFlagClear(data
->flags
, FLG_Activated
))
505 LONG cwidth
= cursor_width
;
507 if(data
->CursorWidth
!= 6)
508 cwidth
= TextLengthNew(&data
->tmprp
, chars
[1] < ' ' ? (char *)" " : (char *)&chars
[1], 1, data
->TabSizePixels
);
510 if(Set
== TRUE
|| data
->currentCursorState
!= CS_INACTIVE
)
512 // draw a "new" skeleton cursor
513 Move(rp
, cursorxplace
, yplace
);
514 Draw(rp
, cursorxplace
+cwidth
-1, yplace
);
515 Draw(rp
, cursorxplace
+cwidth
-1, yplace
+data
->fontheight
-1);
516 Draw(rp
, cursorxplace
, yplace
+data
->fontheight
-1);
517 Draw(rp
, cursorxplace
, yplace
);
520 // remember the inactive state
521 data
->currentCursorState
= CS_INACTIVE
;
525 // draw a normal cursor
526 RectFill(rp
, cursorxplace
, yplace
, cursorxplace
+cursor_width
-1, yplace
+data
->fontheight
-1);
528 // remember the active state
529 data
->currentCursorState
= CS_ACTIVE
;
534 // remember the off state
535 data
->currentCursorState
= CS_OFF
;
539 SetFont(rp
, data
->font
);
540 Move(rp
, xplace
, yplace
+ rp
->TxBaseline
);
542 if(data
->font
->tf_Flags
& FPF_PROPORTIONAL
)
548 for(c
= start
; c
<= stop
; c
++)
550 SetAPen(rp
, ConvertPen(data
, colors
[1+c
], line
->line
.Highlight
));
551 SetSoftStyle(rp
, ConvertStyle(styles
[1+c
]), AskSoftStyle(rp
));
552 TextNew(rp
, (STRPTR
)&chars
[1+c
], 1, data
->TabSizePixels
);
554 SetSoftStyle(rp
, FS_NORMAL
, AskSoftStyle(rp
));
556 /* This is really bad code!!! */
557 if(line
->line
.Separator
!= LNSF_None
)
559 LONG LeftX
, LeftWidth
;
560 LONG RightX
, RightWidth
;
563 LeftX
= _mleft(data
->object
);
565 RightX
= _mleft(data
->object
) + flow
+ TextLengthNew(&data
->tmprp
, &line
->line
.Contents
[pos
.bytes
], pos
.extra
-pos
.bytes
-1, data
->TabSizePixels
) + 3;
566 RightWidth
= _mleft(data
->object
)+_mwidth(data
->object
) - RightX
;
568 Height
= isFlagSet(line
->line
.Separator
, LNSF_Thick
) ? 2 : 1;
570 if(isFlagSet(line
->line
.Separator
, LNSF_Middle
))
571 Y
+= (data
->fontheight
/2)-Height
;
572 else if(isFlagSet(line
->line
.Separator
, LNSF_Bottom
))
573 Y
+= data
->fontheight
-(2*Height
);
575 if(isFlagSet(line
->line
.Separator
, LNSF_StrikeThru
) || line
->line
.Length
== 1)
577 LeftWidth
= _mwidth(data
->object
);
581 DrawSeparator(data
, rp
, RightX
, Y
, RightWidth
, Height
);
583 DrawSeparator(data
, rp
, LeftX
, Y
, LeftWidth
, Height
);
587 RemoveClipping(data
);
596 /*-----------------------------------------*
597 * Dump text from buffer and out to screen *
598 *-----------------------------------------*/
599 void DumpText(struct InstData
*data
, LONG visual_y
, LONG line_nr
, LONG lines
, BOOL doublebuffer
)
602 struct line_node
*line
;
604 BOOL drawbottom
= (visual_y
+ (lines
-line_nr
) - 1) > data
->totallines
;
608 if(data
->update
== TRUE
&& data
->shown
== TRUE
&& isFlagClear(data
->flags
, FLG_Quiet
))
610 GetLine(data
, visual_y
, &pos
);
614 if(lines
-line_nr
< 3 || doublebuffer
== TRUE
)
621 doublebuffer
= FALSE
;
624 while(line
!= NULL
&& line_nr
!= lines
)
626 while(x
< line
->line
.Length
&& line_nr
!= lines
)
627 x
= x
+ PrintLine(data
, x
, line
, ++line_nr
, doublebuffer
);
629 line
= GetNextLine(line
);
633 if(drawbottom
&& (data
->maxlines
> (data
->totallines
-data
->visual_y
+1)))
635 DoMethod(data
->object
, MUIM_DrawBackground
,
636 _mleft(data
->object
),
637 data
->ypos
+((data
->totallines
-data
->visual_y
+1)*data
->fontheight
),
638 _mwidth(data
->object
),
639 (data
->maxlines
*data
->fontheight
) - ((data
->totallines
-data
->visual_y
+1)*data
->fontheight
),
640 (isFlagSet(data
->flags
, FLG_InVGrp
) ? 0 : _mleft(data
->object
)),
641 (isFlagSet(data
->flags
, FLG_InVGrp
) ? 0 : _mtop(data
->object
)) + data
->totallines
*data
->fontheight
,
644 if(isFlagSet(data
->flags
, FLG_Ghosted
) && isFlagClear(data
->flags
, FLG_MUI4
))
648 if(((data
->visual_y
-1)*data
->fontheight
)%2 == 0)
650 newPattern
[0] = 0x4444;
651 newPattern
[1] = 0x1111;
655 newPattern
[0] = 0x1111;
656 newPattern
[1] = 0x4444;
658 SetDrMd(data
->rport
, JAM1
);
659 SetAPen(data
->rport
, _pens(data
->object
)[MPEN_SHADOW
]);
660 SetAfPt(data
->rport
, newPattern
, 1);
661 RectFill(data
->rport
,
662 _mleft(data
->object
),
663 data
->ypos
+((data
->totallines
-data
->visual_y
+1)*data
->fontheight
),
664 _mright(data
->object
),
665 data
->ypos
+((data
->totallines
-data
->visual_y
+1)*data
->fontheight
)+(data
->maxlines
*data
->fontheight
) - ((data
->totallines
-data
->visual_y
+1)*data
->fontheight
)-1);
666 SetAfPt(data
->rport
, NULL
, (UBYTE
)-1);
670 if(doublebuffer
== FALSE
)
671 RemoveClipping(data
);
679 void ScrollUpDown(struct InstData
*data
)
683 if(data
->update
== TRUE
&& isFlagClear(data
->flags
, FLG_Quiet
) && data
->shown
== TRUE
)
685 DumpText(data
, data
->visual_y
, 0, data
->maxlines
, FALSE
);
693 /*----------------------------------------------*
694 * Find a line and fillout a pos_info structure *
695 *----------------------------------------------*/
696 void GetLine(struct InstData
*data
, LONG realline
, struct pos_info
*pos
)
698 struct line_node
*line
= GetFirstLine(&data
->linelist
);
699 struct line_node
*next
;
704 while(realline
> line
->visual
&& (next
= GetNextLine(line
)) != NULL
)
706 realline
= realline
- line
->visual
;
711 pos
->lines
= realline
;
713 if(HasNextLine(line
) == FALSE
&& realline
> line
->visual
)
715 x
= line
->line
.Length
-1;
717 else if(realline
> 0)
721 x
+= LineCharsWidth(data
, &line
->line
.Contents
[x
]);
732 /*----------------------------*
733 * Find visual line on screen *
734 *----------------------------*/
735 LONG
LineToVisual(struct InstData
*data
, struct line_node
*line
)
737 LONG line_nr
= 2 - data
->visual_y
; // Top line!
738 struct line_node
*tline
= GetFirstLine(&data
->linelist
);
742 while(tline
!= line
&& tline
!= NULL
)
744 line_nr
= line_nr
+ tline
->visual
;
745 tline
= GetNextLine(tline
);
754 // count all lines in the list and return the number of visual lines
755 LONG
CountLines(struct InstData
*data
, struct MinList
*lines
)
758 struct line_node
*line
;
762 line
= GetFirstLine(lines
);
767 vh
= VisualHeight(data
, line
);
770 line
= GetNextLine(line
);