MyEditor Canvas based editor gadget

Share your advanced PureBasic knowledge/code with the community.
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

MyEditor Canvas based editor gadget

Post by said »

Hi,

Here is an editor gadget that accepts colors and fonts .... :D
This editor was mainly developed to be an editing area inside a canvas-gadget, can offer an editing area inside custom canvas-gadget (the excellent property box of Danilo or the nice Chart of uwekel .... and inside my MyGrid gadget). It still can be used as an editor on its own.

I never thought an editor would be that complicated :mrgreen: I have no idea how editors are done, i cam up with the below! I still believe things could be done much simpler (i couldn't get any better :!: ) ...

ideas and feedback are welcome

Said

Code: Select all

;- MyEditor PB Module 
;  PB compiler  : 5.40 LTS / 5.30 onwards (ascii/unicode) - All native PB
;  Created on   : Sep-2015
;  
;  Description  : simple editor, defines an editing area inside a canvas object; this area in OpenEditor() is defined by X,Y,W,H
;                 multi-lines, multi-font, multi-colors, handles editing and navigation keys
; Using timer to blink carat - no need for threads
; 
DeclareModule MyEditor
    Enumeration
        #MyEditor_BackColor
        #MyEditor_SelectionBackColor
        #MyEditor_SelectionTextColor
        #MyEditor_CaratColor
        #MyEditor_Align
        #MyEditor_ReadOnly
        #MyEditor_LineSpacing
    EndEnumeration
        
    #MyEditor_FirstCaratPos = 0
    #MyEditor_LastCaratPos  = -1
    
    Declare.i   NoRedraw(Editor)
    Declare.i   Redraw(Editor)
    Declare.i   ManageEvent(Editor.i, eType)
    Declare.i   Resize(Editor.i, X = #PB_Ignore, Y = #PB_Ignore, W = #PB_Ignore, H = #PB_Ignore)
    Declare.i   AddStyle(Editor.i, FontNbr, ForeColor.i)
    Declare.i   AddLineOfText(Editor.i, Txt.s, Align = #PB_Default)
    Declare.i   AssignStyle(Editor, Style, StartPos, Length = -1)
    Declare.s   GetText(Editor)
    Declare.i   SetText(Editor, Txt.s)
    Declare.i   SetCaratPos(Editor, Pos = #MyEditor_LastCaratPos)
    Declare.i   SelectText(Editor, Style, StartPos, Length = -1)
    Declare.i   UseStyle(Editor, Style)
    Declare.i   SetAttribute(Editor, Attribute = #MyEditor_BackColor, Value = $FFFFFF)
    Declare.i   GetAttribute(Editor, Attribute = #MyEditor_BackColor)
    Declare.s   CloseEditor(Editor)
    Declare.i   OpenEditor(WinNbr, Gadget, Text.s, X=#PB_Default, Y=#PB_Default, W=#PB_Default, H=#PB_Default, FontNbr = -1, LineSep.s = #LF$)
    
EndDeclareModule

Module MyEditor
    EnableExplicit
    ; internal constants:
    
    #LineStart      =  1                ; Line start - preceeds each hard line
    #RowStart       =  2                ; Row start - preceeds each artificial row due to wrapping
    #TimerNumber    = 4321
    
    Enumeration
        #Move_Right
        #Move_Left
        #Move_Up
        #Move_Down
        #Move_PageUp
        #Move_PageDown
        #Move_NextWord
        #Move_PreviousWord
        #Move_FirstInRow
        #Move_LastInRow
        #Move_First
        #Move_Last
    EndEnumeration
    
    Structure TChar                     ; rectangle in the drawing area, can be associated with a real char or a line-start
        C.c                             ; actual char/line-start/row-start
        W.u                             ; 
        H.u                             ; for a line-start contain max-height
        S.u                             ; style index in Styles()
        A.a                             ; alignment - valid only for Line (C = LineStart) 
    EndStructure
    
    Structure TCell                     ; a cell is part of the drawing area associated with (Line, Row, Char)
        X.u                             ; X,Y are calculated while drawing, H,W are read from associated Row/Character
        Y.u
        H.i
        
        R.u                             ; index of Row - starting at 1
        I.i                             ; address of associated Char within List: @*E\Chars()
    EndStructure
    
    Structure TStyle
        Font_Id.i                       ; Font ID 
        TxtColor.i                      ; text color
        RowHeight.i
    EndStructure
    
    Structure TEditor
        Window.i
        Gadget.i
        Cursor.i                        ; original canvas cursor, to be restored when exiting editor
        
        X.i                             ; coord of the editor within its parent canvas-gadget
        Y.i
        W.i
        H.i
        BkgColor.i                      ; background color
        SelBColor.i                     ; selection/highlight color
        SelFColor.i                     ; selection/highlight color
        CaratColor.i
        Align.i                         ; 0 / #PB_Text_Right / #PB_Text_Center
        ReadOnly.i                      ; 0/1
        LineSpacing.i                   ; vertical spacing in pixel (default 2)
        AcceptTab.i                     ; 0/1 ???? canvas cant intercept tab-key!
        LineSeparator.s                 ; multi-char text
        
        MarginLeft.i                    ; margins within editor area in pixel
        MarginRight.i                   ; 
        MarginTop.i                     ; 
        MarginDown.i                    ; 
        
        NoDrawing.i
        ; --------------- internal data / use
        List    Chars.TChar()
        List    Cells.TCell()
        Array   Styles.TStyle(0)
        
        TopRow.i
        TokenCarat.i                    ; address of Char holding the carat
        UserStyle.i
        
        SelStart.i                      ; 
        SelEnd.i                        ; 
        LastUsedX.i                     ; needed when moving up/down
        
        CellCarat_X.i
        CellCarat_Y.i
        CellCarat_H.i
        CellCarat_V.i
        
    EndStructure
    
    Global NewMap Editors()             ; list of currently opened editors
    
    Declare     ShowRow(*E.TEditor, Row)
    Declare     RowHavingCarat(*E.TEditor)
    
    Procedure.i MySplitString(s.s, multiCharSep.s, Array a.s(1))
        Protected count, i, soc, lnStr,lnBStr, lnSep,lnBSep, ss, ee
        
        soc     = SizeOf(Character)
        lnSep   = Len(multiCharSep) :   lnBSep  = lnSep * soc
        lnStr   = Len(s)            :   lnBStr  = lnStr * soc
        If lnStr <= 0               :   ProcedureReturn 0       : EndIf
        
        count   = CountString(s,multiCharSep)
        If count <= 0
            Dim a(0) : a(0) = s
            ProcedureReturn 0
        EndIf
        
        Dim a(count)
        
        i = 0 : ss = 0 : ee = 0
        While ee < lnBStr
            If CompareMemory(@s + ee, @multiCharSep, lnBSep)
                a(i) = PeekS(@s + ss, (ee-ss)/soc)
                ss = ee + lnBSep: ee = ss: i+1
            Else
                ee + soc
            EndIf
        Wend
        
        If i < count : a(count) = PeekS(@s + ss, (ee-ss)/soc) : EndIf
        ProcedureReturn count
    EndProcedure
    
    ; ----
    Macro       ResetSelection(Editor)
        Editor\SelStart = 0 : Editor\SelEnd = 0
    EndMacro
    Macro       HasSelection(Editor)
        Bool( (Editor\SelStart <> 0) And (Editor\SelEnd <> 0) And (Editor\SelStart <> Editor\SelEnd) )
    EndMacro
    Macro       IsValueInRange(_I, _S, _E)
        Bool( ((_S < _I) And ( _I < _E)) Or ((_E < _I) And (_I < _S)) )
    EndMacro
    Procedure   AdjustSelection(*E.TEditor, Token)
        If Token
            If *E\SelStart = 0
                *E\SelStart = Token
                *E\SelEnd   = Token
            Else
                *E\SelEnd   = Token
            EndIf
        EndIf
    EndProcedure
    Procedure   IndexOfToken(*E.TEditor, Token)
        Protected   i
        If Token
            PushListPosition(*E\Chars())
            ChangeCurrentElement(*E\Chars(), Token)
            i = ListIndex(*E\Chars())
            PopListPosition(*E\Chars())
        EndIf
        ProcedureReturn i
    EndProcedure
    
    
;---- Lines    
    Procedure   NextWord(*E.TEditor, Starting, MaxWidth)
        ; find the suitable next word that fits in MaxWidth (a space is a word)
        ; Return : 0 --> error / +i ---> Valid / -1 ---> word is too big
        Protected   wdt
        
        If Starting
            ChangeCurrentElement(*E\Chars(), Starting)
            If *E\Chars()\C = #LineStart : ProcedureReturn 0 : EndIf
            If *E\Chars()\C = 32 
                If *E\Chars()\W <= MaxWidth
                    ProcedureReturn Starting
                Else
                    ProcedureReturn -1
                EndIf
            EndIf
            Repeat
                If *E\Chars()\C = 32 Or *E\Chars()\C = #LineStart
                    PreviousElement(*E\Chars())
                    ProcedureReturn @*E\Chars()
                EndIf
                If wdt + *E\Chars()\W <= MaxWidth
                    wdt + *E\Chars()\W
                Else
                    ProcedureReturn -1                      ; next word is too big to fit in available width
                EndIf
                If ListIndex(*E\Chars()) = (ListSize(*E\Chars()) - 1 )
                    ProcedureReturn @*E\Chars()
                EndIf
            Until NextElement(*E\Chars()) = 0
        EndIf
        ProcedureReturn 0                                  ; wrong Starting
        
    EndProcedure
    Procedure   CutWord(*E.TEditor, Starting, MaxWidth)
        ; called when NextWord() fails with -1
        Protected   wdt, ret
        
        If Starting
            ChangeCurrentElement(*E\Chars(), Starting)
            Repeat
                If (*E\Chars()\C = 32) Or (*E\Chars()\C = #LineStart) Or (wdt + *E\Chars()\W > MaxWidth)
                    PreviousElement(*E\Chars())
                    ProcedureReturn @*E\Chars()
                EndIf
                wdt + *E\Chars()\W
                ret = @*E\Chars()
            Until NextElement(*E\Chars()) = 0
        EndIf
        ProcedureReturn ret
        
    EndProcedure
    Procedure   StartOfCurrentLine(*E.TEditor)
        ; return the address of line-start
        Repeat
            If *E\Chars()\C = #LineStart : ProcedureReturn @*E\Chars() : EndIf
        Until PreviousElement(*E\Chars()) = 0
        ProcedureReturn 0
    EndProcedure
    Procedure   BuildRowsOfLine(*E.TEditor, StartLine)
        ; creates Rows of Line starting at StartLine ---> inserts additional #RowStart
        Protected   i,j, wdt_max, wdt_row, str_row, alg_row, s,h, *token
        
        If StartLine = 0 : ProcedureReturn : EndIf
        
        ChangeCurrentElement(*E\Chars(), StartLine)
        alg_row = *E\Chars()\A
        
        ; clean any previous row-start in that line
        While NextElement(*E\Chars())
            ; skip the first #LineStart defining the start of this line
            If str_row = 0 : str_row = @*E\Chars() : EndIf
            If *E\Chars()\C = #LineStart : Break : EndIf
            If *E\Chars()\C = #RowStart
                If *E\TokenCarat = @*E\Chars()
                    If PreviousElement(*E\Chars())
                        *token = @*E\Chars()        ; temporarly places the token on previous element
                        NextElement(*E\Chars())
                    EndIf
                EndIf
                DeleteElement(*E\Chars())
            EndIf
        Wend
        If str_row = 0            ; empty last line
            *E\Chars()\H = *E\Styles(0)\RowHeight
            ProcedureReturn
        EndIf
        
        wdt_max = *E\W - (*E\MarginLeft + *E\MarginRight)
        
        i = str_row
        Repeat
            j = NextWord(*E, i, wdt_max - wdt_row)
            
            If j = 0 : Break : EndIf
            If j > 0
                ChangeCurrentElement(*E\Chars(), i)
                Repeat
                    wdt_row + *E\Chars()\W
                    If @*E\Chars() = j : Break : EndIf
                Until NextElement(*E\Chars()) = 0
                ; i= j+1
                ChangeCurrentElement(*E\Chars(), j)
                If NextElement(*E\Chars()) = 0 : Break : EndIf
                i = @*E\Chars()
                
            Else
                ChangeCurrentElement(*E\Chars(), i)
                If wdt_row = 0
                    j = CutWord(*E, i, wdt_max)
                    ChangeCurrentElement(*E\Chars(), i)
                    Repeat
                        wdt_row + *E\Chars()\W
                        If @*E\Chars() = j : Break : EndIf
                    Until NextElement(*E\Chars()) = 0
                    ; i= j+1
                    ChangeCurrentElement(*E\Chars(), j)
                    If NextElement(*E\Chars()) = 0 : Break : EndIf
                    i = @*E\Chars()
                Else
                    s = *E\Chars()\S
                    h = *E\Chars()\H
                    InsertElement(*E\Chars())
                    *E\Chars()\C    = #RowStart
                    *E\Chars()\A    = alg_row
                    *E\Chars()\S    = s
                    *E\Chars()\H    = h
                    wdt_row = 0
                EndIf
            EndIf
            
        ForEver
        
        ; 
        If *token
            ChangeCurrentElement(*E\Chars(), *token)
            *E\TokenCarat = *token
            If NextElement(*E\Chars())
                If *E\Chars()\C = #RowStart : *E\TokenCarat = @*E\Chars() : EndIf
            EndIf
        EndIf
        
    EndProcedure
    Procedure   BuildRows(*E.TEditor)
        ; creates all Rows
        Protected   NewList lst()
        ForEach *E\Chars()
            If *E\Chars()\C = #LineStart
                AddElement(lst()) : lst() = @*E\Chars()
            EndIf
        Next
        ForEach lst()
            BuildRowsOfLine(*E, lst())
        Next
        
    EndProcedure
    Procedure   DeleteSelection(*E.TEditor)
        Protected i, n, i1, i2, t1,t2, L
        
        If HasSelection(*E) = #False : ProcedureReturn : EndIf
        
        t1 = *E\SelStart    : i1 = IndexOfToken(*E, t1)
        t2 = *E\SelEnd      : i2 = IndexOfToken(*E, t2)
        If i1 > i2
            Swap i1, i2 : Swap t1, t2
        EndIf
        n = i2 - i1 
        If ChangeCurrentElement(*E\Chars(), t2)
            Repeat
                DeleteElement(*E\Chars()) : i + 1
                *E\TokenCarat = @*E\Chars()
                If i >= n : Break : EndIf
            Until ListSize(*E\Chars()) <= 1
        EndIf
        
        L = StartOfCurrentLine(*E)
        BuildRowsOfLine(*E, L)
        ResetSelection(*E)
        
    EndProcedure
    
    Procedure.i StartOfRow(*E.TEditor, Row)
        Protected   n
        If Row > 0
            ForEach *E\Chars()
                If *E\Chars()\C = #LineStart Or *E\Chars()\C = #RowStart : n+1 : EndIf
                If n = Row
                    ProcedureReturn @*E\Chars()
                EndIf
            Next
        EndIf
        ProcedureReturn 0
    EndProcedure
    Procedure.i EndOfRow(*E.TEditor, StartRow)
        Protected   ret
        If StartRow
            ChangeCurrentElement(*E\Chars(), StartRow)
            ret = StartRow
            While NextElement(*E\Chars())
                If *E\Chars()\C = #LineStart Or *E\Chars()\C = #RowStart : Break : EndIf
                ret = @*E\Chars()
            Wend
        EndIf
        ProcedureReturn ret
    EndProcedure
    Procedure.i NextRow(*E.TEditor, Token)
        ; return the start of row coming AFTER Token, Token is an address in Chars()
        If Token
            ChangeCurrentElement(*E\Chars(), Token)
            While NextElement(*E\Chars())
                If *E\Chars()\C = #LineStart Or *E\Chars()\C = #RowStart : ProcedureReturn @*E\Chars() : EndIf
            Wend
        EndIf
        ProcedureReturn 0
    EndProcedure
        
    Procedure.i AddChar(*E.TEditor, C.c)
        ; user input, usual text adds the char C after current Carat
        Protected   i,n, img, L, s
        
        If *E\ReadOnly : ProcedureReturn : EndIf
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        s = *E\UserStyle : If s < 0 : s = *E\Chars()\S : EndIf
        
        L = StartOfCurrentLine(*E)
        
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        If AddElement(*E\Chars())
            *E\TokenCarat = @*E\Chars()
            Img = CreateImage(#PB_Any, 1, 1)
            If Img And StartDrawing(ImageOutput(Img))
                DrawingFont( *E\Styles(s)\Font_Id )
                *E\Chars()\W = TextWidth( Chr(C) )
                *E\Chars()\H = TextHeight( Chr(C) )
                *E\Chars()\C = C
                *E\Chars()\S = s
                StopDrawing()
                FreeImage(Img)
            EndIf
            BuildRowsOfLine(*E, L)
            ProcedureReturn #True
        EndIf
        
        ProcedureReturn #False
        
    EndProcedure
    Procedure.i ProcessKey(*E.TEditor, Ky)
        ; user input, special key (Backspace, del, enter, ...)
        Protected   i,a,s,h, L
        
        If *E\ReadOnly : ProcedureReturn : EndIf
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        h = *E\Chars()\H
        Select ky
            Case #PB_Shortcut_Back
                If ListIndex(*E\Chars()) > 0
                    While *E\Chars()\C = #RowStart : PreviousElement(*E\Chars()) : Wend
                    DeleteElement(*E\Chars())
                    *E\TokenCarat = @*E\Chars()
                    L = StartOfCurrentLine(*E)
                    BuildRowsOfLine(*E, L)
                EndIf
                
            Case #PB_Shortcut_Delete
                If NextElement(*E\Chars())
                    While *E\Chars()\C = #RowStart : NextElement(*E\Chars()) : Wend
                    DeleteElement(*E\Chars())
                    L = StartOfCurrentLine(*E)
                    BuildRowsOfLine(*E, L)
                EndIf
                
            Case #PB_Shortcut_Return
                L = StartOfCurrentLine(*E)
                a = *E\Chars()\A
                ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
                If AddElement(*E\Chars())
                    *E\Chars()\C = #LineStart
                    *E\Chars()\H = h
                    *E\Chars()\A = a
                    *E\TokenCarat = @*E\Chars()
                    
                    ;BuildRows(*E)
                    BuildRowsOfLine(*E, @*E\Chars())
                    BuildRowsOfLine(*E, L)
                    ProcedureReturn #True
                EndIf
                
            Case #PB_Shortcut_Tab       ; ?
        EndSelect
        
        ProcedureReturn #False
        
    EndProcedure
    
    Macro       AddCell(Editor, _X,_Y,_H, _R,_I)
        AddElement(Editor\Cells())
        Editor\Cells()\X = _X
        Editor\Cells()\Y = _Y
        Editor\Cells()\H = _H
        Editor\Cells()\R = _R
        Editor\Cells()\I = _I
    EndMacro
    
    Procedure.i BuildCells(*E.TEditor)
        ; 
        Protected   i, x, y, w, h, row, s,e, a, wdt_max, hgt_max

        wdt_max = *E\W - (*E\MarginLeft + *E\MarginRight)
        hgt_max = *E\H - (*E\MarginTop + *E\MarginDown)
        x       = *E\MarginLeft
        y       = *E\MarginTop
        
        ; we start drawing at TopRow
        If *E\TopRow < 1 : *E\TopRow = 1 : EndIf
        row = *E\TopRow
        s = StartOfRow(*E, row)
        
        ClearList(*E\Cells())
        Repeat
            If s <= 0 : Break : EndIf
            e = EndOfRow(*E, s)
            w = 0 : h = 0
            
            ChangeCurrentElement(*E\Chars(), s)
            a = *E\Chars()\A
            h = *E\Chars()\H
            Repeat
                w + *E\Chars()\W
                If h < *E\Chars()\H : h = *E\Chars()\H : EndIf
                If @*E\Chars() = e : Break : EndIf
            Until NextElement(*E\Chars()) = 0
            
            If h <= 0 : h = *E\Styles(0)\RowHeight : EndIf
            If (y + h + *E\LineSpacing) > hgt_max : Break : EndIf
            
            Select a                                 ; we set starting x
                Case #PB_Text_Right
                    x = *E\MarginLeft + (wdt_max - w)
                Case #PB_Text_Center
                    x = *E\MarginLeft + Round((wdt_max - w)/2, #PB_Round_Up)
                Default
                    x = *E\MarginLeft
            EndSelect
            
            ChangeCurrentElement(*E\Chars(), s)
            Repeat
                x + *E\Chars()\W
                AddCell(*E, x, y + (h - *E\Chars()\H), *E\Chars()\H, row, @*E\Chars())
                If @*E\Chars() = e : Break : EndIf
            Until NextElement(*E\Chars()) = 0
            
            s = NextRow(*E, e)
            row + 1
            y = y + h + *E\LineSpacing
        ForEver

    EndProcedure
    Procedure   CellOfCarat(*E.TEditor)
        ForEach *E\Cells()
            If *E\Cells()\I = *E\TokenCarat
                *E\CellCarat_X = *E\Cells()\X
                *E\CellCarat_Y = *E\Cells()\Y
                *E\CellCarat_H = *E\Cells()\H
                *E\CellCarat_V = #True
                ProcedureReturn #True
            EndIf
        Next
        *E\CellCarat_V = #False
        ProcedureReturn #False
    EndProcedure
    Procedure   BlinkCaratTimer()
        Protected   *E.TEditor
        Static i
        
        If GetActiveWindow() = EventWindow()
            ForEach Editors()
                *E = Val(MapKey(Editors()))
                If *E\Window <> EventWindow()       : Continue : EndIf
                If *E\Gadget <> GetActiveGadget()   : Continue : EndIf

                If *E\CellCarat_V And StartDrawing(CanvasOutput(*E\Gadget))
                    SetOrigin(*E\X, *E\Y)
                    DrawingMode(#PB_2DDrawing_Default)
                    If i
                        Line(*E\CellCarat_X, *E\CellCarat_Y, 1, *E\CellCarat_H, *E\BkgColor)
                        i = 0
                    Else
                        Line(*E\CellCarat_X, *E\CellCarat_Y, 1, *E\CellCarat_H, *E\CaratColor)
                        i = 1
                    EndIf
                    StopDrawing()
                EndIf
            Next
        EndIf
        
    EndProcedure
    Procedure   AddCaratTimer(WinNbr)
        ; adds a timer to WinNbr with value #TimerNumber if not previousely added
        Protected   *E.TEditor, exist
        
        ForEach Editors()
            *E = Val(MapKey(Editors()))
            If *E\Window = WinNbr : exist = #True : Break : EndIf
        Next
        
        If (exist = #False) And IsWindow(WinNbr)
            AddWindowTimer(WinNbr, #TimerNumber, 400)
            BindEvent(#PB_Event_Timer , @BlinkCaratTimer(), WinNbr)
        EndIf
    EndProcedure
    Procedure   RemoveCaratTimer(*E.TEditor)
        ; removes the timer #TimerNumber if no longer needed - last editor in that window about to be closed
        Protected   *t.TEditor, n
        
        ForEach Editors()
            *t = Val(MapKey(Editors()))
            If *t\Window = *E\Window : n+1 : EndIf
        Next
        
        If n = 1
            UnbindEvent(#PB_Event_Timer , @BlinkCaratTimer(), *E\Window)
            RemoveWindowTimer(*E\Window, #TimerNumber)
        EndIf
    EndProcedure
    
    Procedure.i DrawEditor(*E.TEditor, OptBuildCells = #True, OptShowRowHavingCarat = #True)
        ; draw as per current settings/counters
        Protected   i,j,n, x, y, h, Img, c.c, fClr, bClr, t, sel_s.f, sel_e.f
        Protected   carat_pos, cur_row, wdt_max, hgt_max
        
        If *E\NoDrawing = #True : ProcedureReturn : EndIf
        
        wdt_max = *E\W - (*E\MarginLeft + *E\MarginRight)
        hgt_max = *E\H - (*E\MarginTop + *E\MarginDown)
        x       = *E\MarginLeft
        y       = *E\MarginTop
        
        t = ElapsedMilliseconds()
        
        If OptBuildCells : BuildCells(*E) : EndIf
        If OptShowRowHavingCarat
            If ShowRow(*E, RowHavingCarat(*E)) : BuildCells(*E) : EndIf
        EndIf
        
        If HasSelection(*E)
            sel_s = IndexOfToken(*E, *E\SelStart) + 0.5
            sel_e = IndexOfToken(*E, *E\SelEnd) + 0.5
        EndIf
        
        If StartDrawing(CanvasOutput(*E\Gadget))
            SetOrigin(*E\X, *E\Y)
            DrawingMode(#PB_2DDrawing_Default)
            
            bClr = *E\BkgColor
            Box(0,0,*E\W, *E\H,bClr)
            
            If FirstElement(*E\Cells())
                If *E\Cells()\I
                    ChangeCurrentElement(*E\Chars(), *E\Cells()\I)
                    Repeat
                        c = *E\Chars()\C
                        If c = #LineStart Or c = #RowStart
                            h = *E\Chars()\H
                            PushListPosition(*E\Chars())
                            While NextElement(*E\Chars())
                                If *E\Chars()\C = #LineStart Or *E\Chars()\C = #RowStart : Break : EndIf
                                If h < *E\Chars()\H : h = *E\Chars()\H : EndIf
                            Wend
                            PopListPosition(*E\Chars())
                            If h <= 0 : h = *E\Styles(*E\Chars()\S)\RowHeight : EndIf
                        Else
                            x = *E\Cells()\X - *E\Chars()\W
                            y = *E\Cells()\Y 
                            
                            DrawingFont( *E\Styles(*E\Chars()\S)\Font_Id )
                            fClr = *E\Styles(*E\Chars()\S)\TxtColor
                            If HasSelection(*E) And IsValueInRange(0.0 + ListIndex(*E\Chars()), sel_s, sel_e)
                                Box(x, y - (h - *E\Chars()\H), *E\Chars()\W, h, *E\SelBColor)
                                DrawText(x, y, Chr(c), *E\SelFColor, *E\SelBColor)
                            Else
                                DrawText(x, y, Chr(c), fClr, bClr)
                            EndIf
                        EndIf
                        NextElement(*E\Chars())
                    Until NextElement(*E\Cells()) = 0
                EndIf
            EndIf
            
            ; drawing carat
            If CellOfCarat(*E)
                Line(*E\Cells()\X, *E\Cells()\Y, 1, *E\Cells()\H, *E\CaratColor)
            EndIf
            
            StopDrawing()
        EndIf
        Debug " >>>>>>>>>>>>>>>>> DrawEditor time: " + Str( ElapsedMilliseconds() - t)
        

    EndProcedure
    
    ;-------------------------------------------------------------------------------
    Procedure   LastVisibleRow(*E.TEditor)
        If LastElement(*E\Cells()) : ProcedureReturn *E\Cells()\R : EndIf
    EndProcedure
    Procedure   RowHavingCarat(*E.TEditor)
        Protected n
        ForEach *E\Chars()
            If *E\Chars()\C = #LineStart Or *E\Chars()\C = #RowStart : n + 1 : EndIf
            If @*E\Chars() = *E\TokenCarat : Break : EndIf
        Next
        ProcedureReturn n
    EndProcedure
    
    Procedure.i ShowRow(*E.TEditor, Row)
        ; makes sure the row defined by Row is visible, scrolls if needed ---> cahnges *E\TopRow
        Protected   i, y, hgt_max, h, old_top, s,e
        
        If Row <= 0 : ProcedureReturn #False : EndIf
        old_top = *E\TopRow
        
        If Row < *E\TopRow 
            *E\TopRow = Row
        Else 
            If LastElement(*E\Cells())
                If Row > *E\Cells()\R
                    hgt_max = *E\H - (*E\MarginTop + *E\MarginDown)
                    y       = *E\MarginTop
                    i       = Row
                    
                    s = StartOfRow(*E, Row)
                    If s
                        e = EndOfRow(*E, s)
                        ChangeCurrentElement(*E\Chars(), e)
                        
                        While i >= 1
                            h = 0
                            Repeat
                                If *E\Chars()\C = #LineStart Or *E\Chars()\C = #RowStart : Break : EndIf
                                If h < *E\Chars()\H : h = *E\Chars()\H : EndIf
                            Until PreviousElement(*E\Chars()) = 0
                            If h <= 0 : h = *E\Styles(*E\Chars()\S)\RowHeight : EndIf
                            
                            y = y + (h + *E\LineSpacing)
                            If y > hgt_max : Break : EndIf
                            
                            *E\TopRow   = i
                            i - 1
                            If PreviousElement(*E\Chars()) = 0 : Break : EndIf
                        Wend
                        
                    EndIf
                    
                EndIf
            EndIf
        EndIf
        ProcedureReturn Bool(old_top <> *E\TopRow)
        
    EndProcedure
    Procedure.i RelativeX(*E.TEditor, Canvas_X)
        ; return the reltive x within editor of the passed x based on parent-canvas
        Protected x
        
        If Canvas_X < *E\X              : x = 0
        ElseIf Canvas_X > *E\X + *E\W   : x = *E\W
        Else                            : x = Canvas_X - *E\X
        EndIf
        ProcedureReturn x
    EndProcedure
    Procedure.i RelativeY(*E.TEditor, Canvas_Y)
        Protected y
        
        If Canvas_Y < *E\Y              : y = 0
        ElseIf Canvas_Y > *E\Y + *E\H   : y = *E\H
        Else                            : y = Canvas_Y - *E\Y
        EndIf
        ProcedureReturn y
    EndProcedure
    
    
    ; X, Y are coordinates within editor area (relative to editor coord)
    ; ----
    Procedure   SetCaratFromXY(*E.TEditor, X, Y)
        Protected   min_x, min_y, dlt, idx_r, idx_s, idx_v, rr
        
        min_x = *E\W            : min_y = *E\H
        idx_s = -1              : idx_v = -1
        
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        
        ForEach *E\Cells()                 ; locating closest row
            If *E\Cells()\R > rr           ; rr = last checked row
                dlt     = Abs(*E\Cells()\Y + (*E\Cells()\H / 2) - Y)
                If dlt < min_y
                    min_y = dlt
                    idx_s = ListIndex(*E\Cells())
                    idx_r = *E\Cells()\R
                EndIf
                rr = *E\Cells()\R
            EndIf
        Next
        
        If idx_s >= 0 And SelectElement(*E\Cells(), idx_s)
            Repeat
                If *E\Cells()\R > idx_r : Break : EndIf
                dlt     = Abs(*E\Cells()\X - x)
                If dlt < min_x
                    min_x = dlt
                    idx_v = ListIndex(*E\Cells())
                EndIf
            Until NextElement(*E\Cells()) = 0
        EndIf
        
        If idx_v >= 0 And SelectElement(*E\Cells(), idx_v)
            If *E\TokenCarat <> *E\Cells()\I
                *E\TokenCarat   = *E\Cells()\I
                *E\LastUsedX    = *E\Cells()\X
                *E\UserStyle    = -1
            EndIf
        EndIf
        
    EndProcedure
    Procedure   NextCarat(*E.TEditor)
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        If NextElement(*E\Chars()) : *E\TokenCarat   = @*E\Chars() : EndIf
    EndProcedure
    Procedure   PreviousCarat(*E.TEditor)
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        If PreviousElement(*E\Chars()) : *E\TokenCarat   = @*E\Chars() : EndIf
    EndProcedure
    Procedure   DownCarat(*E.TEditor, NbrRows = 1)
        Protected i, x, dlt, mn = *E\W
        If CellOfCarat(*E)
            i = *E\Cells()\R + NbrRows
        Else
            i = RowHavingCarat(*E) + NbrRows
        EndIf
        x = *E\LastUsedX
        
        If ShowRow(*E, i) : BuildCells(*E) : EndIf
        ForEach *E\Cells()
            If *E\Cells()\R = i
                dlt = Abs(*E\Cells()\X - x)
                If dlt <= mn : *E\TokenCarat = *E\Cells()\I : mn = dlt : EndIf
            EndIf
            If *E\Cells()\R > i : Break : EndIf
        Next
        
    EndProcedure
    Procedure   UpCarat(*E.TEditor, NbrRows = 1)
        Protected i, x, dlt, mn = *E\W
        If CellOfCarat(*E)
            i = *E\Cells()\R - NbrRows
        Else
            i = RowHavingCarat(*E) - NbrRows
        EndIf
        
        x = *E\LastUsedX
        If ShowRow(*E, i) : BuildCells(*E) : EndIf
        ForEach *E\Cells()
            If *E\Cells()\R = i
                dlt = Abs(*E\Cells()\X - x)
                If dlt <= mn : *E\TokenCarat = *E\Cells()\I : mn = dlt : EndIf
            EndIf
            If *E\Cells()\R > i : Break : EndIf
        Next
        
    EndProcedure
    Procedure   FirstCaratInRow(*E.TEditor)
        Protected R
        If CellOfCarat(*E) 
            R = *E\Cells()\R
            While PreviousElement(*E\Cells())
                If R <> *E\Cells()\R : Break : EndIf
                *E\TokenCarat   = *E\Cells()\I
            Wend
        EndIf
    EndProcedure
    Procedure   LastCaratInRow(*E.TEditor)
        Protected R
        If CellOfCarat(*E) 
            R = *E\Cells()\R
            While NextElement(*E\Cells())
                If R <> *E\Cells()\R : Break : EndIf
                *E\TokenCarat   = *E\Cells()\I
            Wend
        EndIf
    EndProcedure
    Procedure   NextWordCarat(*E.TEditor)
        Protected i = -1
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        While NextElement(*E\Chars())
            If *E\Chars()\C = 32 Or *E\Chars()\C = #LineStart : i = ListIndex(*E\Chars()) : Break : EndIf
        Wend
        If i >= 0
            While *E\Chars()\C = 32
                i = ListIndex(*E\Chars())
                If NextElement(*E\Chars()) = 0 : Break : EndIf
            Wend
            SelectElement(*E\Chars(), i)
            *E\TokenCarat   = @*E\Chars()
        EndIf
        
    EndProcedure
    Procedure   PreviousWordCarat(*E.TEditor)
        ChangeCurrentElement(*E\Chars(), *E\TokenCarat)
        While PreviousElement(*E\Chars())
            If *E\Chars()\C = 32 Or *E\Chars()\C = #LineStart : *E\TokenCarat = @*E\Chars() : Break : EndIf
        Wend
    EndProcedure
    Procedure   MoveCarat(*E.TEditor, Direction, sel = 0)
        Protected upd_x = #True, old_c = *E\TokenCarat
        
        If sel
            AdjustSelection(*E, *E\TokenCarat)
        Else
            ResetSelection(*E)
        EndIf
        Select Direction
            Case #Move_Right
                NextCarat(*E)
                
            Case #Move_Left
                PreviousCarat(*E)
                
            Case #Move_Down
                DownCarat(*E) : upd_x = #False
                
            Case #Move_Up
                UpCarat(*E) : upd_x = #False
                
            Case #Move_PageDown
                DownCarat(*E, 20) : upd_x = #False
                
            Case #Move_PageUp
                UpCarat(*E, 20) : upd_x = #False
                
            Case #Move_FirstInRow
                FirstCaratInRow(*E)
                
            Case #Move_LastInRow
                LastCaratInRow(*E)
        
            Case #Move_First
                *E\TopRow       = 1
                FirstElement(*E\Chars())
                *E\TokenCarat = @*E\Chars()
                
            Case #Move_Last
                LastElement(*E\Chars())
                *E\TokenCarat = @*E\Chars()
                
            Case #Move_NextWord
                NextWordCarat(*E)
                
            Case #Move_PreviousWord
                PreviousWordCarat(*E)
                
        EndSelect
        
        If sel : AdjustSelection(*E, *E\TokenCarat) : EndIf
        If old_c <> *E\TokenCarat : DrawEditor(*E) : EndIf
        If upd_x
            If CellOfCarat(*E) : *E\LastUsedX = *E\Cells()\X : EndIf
        EndIf
        *E\UserStyle = -1
        
    EndProcedure
    Procedure   RelativePos(*E.TEditor, Pos)
        Protected   n, sep = Len(*E\LineSeparator)
        
        If Pos <= 0 Or Pos > ListSize(*E\Chars()) : ProcedureReturn -1 : EndIf
        
        If FirstElement(*E\Chars())
            While NextElement(*E\Chars())
                If *E\Chars()\C = #RowStart : Continue : EndIf
                If *E\Chars()\C = #LineStart
                    n + sep
                Else
                    n + 1
                EndIf
                If n = Pos : ProcedureReturn ListIndex(*E\Chars()) : EndIf
            Wend
        EndIf
        ProcedureReturn -1
    EndProcedure
    
    ;---- Interface
    Procedure.i NoRedraw(Editor)
        Protected   *E.TEditor = Editor
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        *E\NoDrawing = #True
    EndProcedure
    Procedure.i Redraw(Editor)
        Protected   *E.TEditor = Editor
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        *E\NoDrawing = #False
        DrawEditor(*E)
    EndProcedure
    Procedure.i SetAttribute(Editor, Attribute = #MyEditor_BackColor, Value = $FFFFFF)
        Protected   *E.TEditor = Editor
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        If Value < 0 : ProcedureReturn : EndIf
        
        Select Attribute
            Case #MyEditor_BackColor            : *E\BkgColor = Value
            Case #MyEditor_SelectionBackColor   : *E\SelBColor = Value
            Case #MyEditor_SelectionTextColor   : *E\SelFColor = Value
            Case #MyEditor_CaratColor           : *E\CaratColor = Value
            Case #MyEditor_Align                : *E\Align = Value
            Case #MyEditor_ReadOnly             : *E\ReadOnly = Value
            Case #MyEditor_LineSpacing          : *E\LineSpacing = Value
        EndSelect
        
    EndProcedure
    Procedure.i GetAttribute(Editor, Attribute = #MyEditor_BackColor)
        Protected   V, *E.TEditor = Editor
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        
        Select Attribute
            Case #MyEditor_BackColor            : V = *E\BkgColor
            Case #MyEditor_SelectionBackColor   : V = *E\SelBColor
            Case #MyEditor_SelectionTextColor   : V = *E\SelFColor
            Case #MyEditor_CaratColor           : V = *E\CaratColor
            Case #MyEditor_Align                : V = *E\Align
            Case #MyEditor_ReadOnly             : V = *E\ReadOnly
            Case #MyEditor_LineSpacing          : V = *E\LineSpacing
        EndSelect
        ProcedureReturn V
    EndProcedure
    Procedure.i SetCaratPos(Editor, Pos = #MyEditor_LastCaratPos)
        Protected   *E.TEditor = Editor
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        
        Protected   i
        Select Pos
            Case #MyEditor_FirstCaratPos
                If FirstElement(*E\Chars()) : *E\TokenCarat = @*E\Chars() : EndIf
            Case #MyEditor_LastCaratPos
                If LastElement(*E\Chars())  : *E\TokenCarat = @*E\Chars() : EndIf
            Default
                i = RelativePos(*E, Pos)
                If i >= 0  And SelectElement(*E\Chars(), i)
                    *E\TokenCarat = @*E\Chars() : 
                EndIf
        EndSelect
        DrawEditor(*E)
        
    EndProcedure
    Procedure.i SelectText(Editor, Style, Start, Length = -1)
        Protected   *E.TEditor = Editor
        Protected   s,e
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        
        s = RelativePos(*E, Start)
        If Length = -1
            e = ListSize(*E\Chars()) - 1
        Else
            e = RelativePos(*E, Start+Length-1)
        EndIf
        If s = -1 Or e = -1 : ProcedureReturn : EndIf
        If SelectElement(*E\Chars(), s)
            PreviousElement(*E\Chars())
            s = @*E\Chars()
            If SelectElement(*E\Chars(), e)
                *E\SelStart = s
                *E\SelEnd = @*E\Chars()
            EndIf
        EndIf
        
        DrawEditor(*E)
        
    EndProcedure
    
    Procedure.i ManageEvent(Editor.i, eType)
        ; Gdt is the canvas gadget that first received this event and passed it to its associated Editor
        Protected   Gdt.i, *E.TEditor = Editor
        Protected   ky,mf, x,y, dlt,i, sel
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        Gdt = *E\Gadget
        Select eType
                
            Case #PB_EventType_KeyDown
                ky = GetGadgetAttribute(gdt, #PB_Canvas_Key )
                mf = GetGadgetAttribute(gdt, #PB_Canvas_Modifiers )
                
                If mf & #PB_Canvas_Shift : sel = 1 : EndIf
                ;If sel = 0 : ResetSelection(*E) : EndIf
                
                If mf & #PB_Canvas_Control
                    Select ky
                        Case #PB_Shortcut_Left      : MoveCarat(*E, #Move_PreviousWord, sel)
                        Case #PB_Shortcut_Right     : MoveCarat(*E, #Move_NextWord, sel)
                        Case #PB_Shortcut_Up        : 
                        Case #PB_Shortcut_Down      : 
                        Case #PB_Shortcut_Home      : MoveCarat(*E, #Move_First, sel)
                        Case #PB_Shortcut_End       : MoveCarat(*E, #Move_Last, sel)
                        Case #PB_Shortcut_PageUp    : MoveCarat(*E, #Move_PageUp, sel)
                        Case #PB_Shortcut_PageDown  : MoveCarat(*E, #Move_PageDown, sel)
                    EndSelect
                Else
                    Select ky
                        Case #PB_Shortcut_Left      : MoveCarat(*E, #Move_Left, sel)
                        Case #PB_Shortcut_Right     : MoveCarat(*E, #Move_Right, sel)
                        Case #PB_Shortcut_Up        : MoveCarat(*E, #Move_Up, sel)
                        Case #PB_Shortcut_Down      : MoveCarat(*E, #Move_Down, sel)
                        Case #PB_Shortcut_Home      : MoveCarat(*E, #Move_FirstInRow, sel)
                        Case #PB_Shortcut_End       : MoveCarat(*E, #Move_LastInRow, sel)
                        Case #PB_Shortcut_PageUp    : MoveCarat(*E, #Move_PageUp, sel)
                        Case #PB_Shortcut_PageDown  : MoveCarat(*E, #Move_PageDown, sel)
                    EndSelect
                EndIf
                
                Select ky
                    Case #PB_Shortcut_Delete, #PB_Shortcut_Back, #PB_Shortcut_Tab
                        If HasSelection(*E)
                            DeleteSelection(*E)
                        Else
                            ProcessKey(*E, Ky)
                        EndIf
                        DrawEditor(*E)
                        
                    Case #PB_Shortcut_Return
                        DeleteSelection(*E)
                        ProcessKey(*E, Ky)
                        DrawEditor(*E)
                EndSelect
                
            Case #PB_EventType_Input                                ; add a new char after current element
                    DeleteSelection(*E)
                    AddChar(*E, GetGadgetAttribute(gdt, #PB_Canvas_Input))
                    DrawEditor(*E)
                
            Case #PB_EventType_MouseWheel
                dlt = GetGadgetAttribute(gdt, #PB_Canvas_WheelDelta)
                If dlt < 0
                    If LastElement(*E\Cells())
                        If ShowRow(*E, *E\Cells()\R + 1)
                            DrawEditor(*E, #True, #False)
                        EndIf
                    EndIf
                ElseIf dlt > 0
                    If FirstElement(*E\Cells())
                        If ShowRow(*E, *E\Cells()\R - 1)
                            DrawEditor(*E, #True, #False)
                        EndIf
                    EndIf
                EndIf
                
            Case #PB_EventType_LeftDoubleClick
            Case #PB_EventType_MouseEnter
            Case #PB_EventType_MouseMove                ; selecting via mouse
                x = RelativeX(*E, GetGadgetAttribute(gdt, #PB_Canvas_MouseX))
                y = RelativeY(*E, GetGadgetAttribute(gdt, #PB_Canvas_MouseY))
                
                If GetGadgetAttribute(gdt, #PB_Canvas_Buttons) = #PB_Canvas_LeftButton
                    If y >= *E\H - *E\MarginDown
                        i = LastVisibleRow(*E) + 1
                        If ShowRow(*E, i)
                            BuildCells(*E)
                        EndIf
                    ElseIf y <= *E\MarginTop
                        i = *E\TopRow - 1
                        If ShowRow(*E, i)
                            BuildCells(*E)
                        EndIf
                    EndIf
                    
                    SetCaratFromXY(*E, x, y)
                    AdjustSelection(*E, *E\TokenCarat)
                    DrawEditor(*E, #False, #True)
                EndIf
                
            Case #PB_EventType_MouseLeave
            Case #PB_EventType_LeftButtonUp
            Case #PB_EventType_LeftButtonDown
                x = RelativeX(*E, GetGadgetAttribute(gdt, #PB_Canvas_MouseX))
                y = RelativeY(*E, GetGadgetAttribute(gdt, #PB_Canvas_MouseY))
                ResetSelection(*E)
                SetCaratFromXY(*E, x, y)
                DrawEditor(*E, #False, #False)
                
            Case #PB_EventType_RightButtonDown
                
            Default
                ProcedureReturn #False 
        EndSelect
        

    EndProcedure
    Procedure.i Resize(Editor.i, X = #PB_Ignore, Y = #PB_Ignore, W = #PB_Ignore, H = #PB_Ignore)
        Protected   *E.TEditor = Editor
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        If X <> #PB_Ignore : *E\X = X : EndIf
        If Y <> #PB_Ignore : *E\Y = Y : EndIf
        If W <> #PB_Ignore : *E\W = W : EndIf
        If H <> #PB_Ignore : *E\H = H : EndIf
        BuildRows(*E)
        DrawEditor(*E)

    EndProcedure
    Procedure.i AddStyle(Editor.i, FontNbr, ForeColor.i)
        Protected   *E.TEditor = Editor, img, n
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        n = ArraySize(*E\Styles())
        If *E\Styles(n)\RowHeight > 0
            n = n + 1
            ReDim *E\Styles(n)
        EndIf
        
        *E\Styles(n)\TxtColor       = ForeColor
        If IsFont(FontNbr)
            *E\Styles(n)\Font_Id    = FontID(FontNbr)
        Else
            *E\Styles(n)\Font_Id    = #PB_Default
        EndIf
        Img = CreateImage(#PB_Any, 1, 1)
        If Img 
            If StartDrawing(ImageOutput(Img))
                DrawingFont(*E\Styles(n)\Font_Id)
                *E\Styles(n)\RowHeight = TextHeight("A")
                StopDrawing()
            EndIf
            FreeImage(Img)
        EndIf
        ProcedureReturn n
        
    EndProcedure
    Procedure.i AddLineOfText(Editor.i, Txt.s, Align = #PB_Default)
        ; adds a new line of text at the end
        Protected   *E.TEditor = Editor
        Protected   i, n, img, s, *c.Character, L
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        If Align = #PB_Default : Align = *E\Align : EndIf
        
        n = FindString(Txt, *E\LineSeparator) - 1
        If n = -1 : n = Len(Txt) : EndIf
        
        LastElement(*E\Chars())
        s = *E\UserStyle : If s < 0 : s = *E\Chars()\S : EndIf
        If AddElement(*E\Chars())
            *E\Chars()\C = #LineStart
            *E\Chars()\A = Align
            *E\Chars()\S = s
            *E\Chars()\H = *E\Styles(s)\RowHeight
            L = @*E\Chars()
        EndIf
        
        Img = CreateImage(#PB_Any, 1, 1)
        If Img 
            If StartDrawing(ImageOutput(Img))
                DrawingFont(*E\Styles(s)\Font_Id)
                For i = 1 To n
                    *c = @Txt + ((i-1) * SizeOf(Character))
                    If AddElement(*E\Chars())
                        *E\Chars()\C = *c\c
                        *E\Chars()\H = TextHeight( Chr(*c\c) )
                        *E\Chars()\W = TextWidth(  Chr(*c\c) )
                        *E\Chars()\S = s
                        
                        *E\TokenCarat = @*E\Chars()
                    EndIf
                Next
                StopDrawing()
            EndIf
            FreeImage(Img)
        EndIf
        BuildRowsOfLine(*E, L)
        ProcedureReturn #True
        
    EndProcedure
    
    Procedure.i SetText(Editor, Txt.s)
        Protected   *E.TEditor = Editor
        Protected   i, Dim a.s(0)
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        MySplitString(Txt, *E\LineSeparator, a())
        For i=0 To ArraySize(a())
            AddLineOfText(*E, a(i))
        Next
        
    EndProcedure
    Procedure.s GetText(Editor)
        Protected   *E.TEditor = Editor
        Protected   i, ret.s, NewList sp.c(), NewList txt.c()
        
        If Editors(Str(*E)) = 0 : ProcedureReturn "" : EndIf
        For i = 1 To Len(*E\LineSeparator)
            If AddElement(sp()) : sp() = Asc(Mid(*E\LineSeparator, i, 1)) : EndIf
        Next
        
        If FirstElement(*E\Chars())
            While NextElement(*E\Chars())
                If *E\Chars()\C = #RowStart  : Continue : EndIf
                If *E\Chars()\C = #LineStart
                    ForEach sp()
                        AddElement(txt()) : txt() = sp()
                    Next
                    ;MergeLists(sp(), txt(), #PB_List_Last) : LastElement(txt())
                Else
                    AddElement(txt())
                    txt() = *E\Chars()\C
                EndIf
            Wend
        EndIf
        ret = Space(ListSize(txt()))
        i = 0
        ForEach txt()
            PokeC(@ret + (i * SizeOf(Character)), txt())
            i+1
        Next
        ProcedureReturn ret
        
    EndProcedure
    Procedure.i AssignStyle(Editor, Style, Start, Length = -1)
        Protected   *E.TEditor = Editor
        Protected   s,e, L, img
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        If Style < 0 Or Style > ArraySize(*E\Styles()) : ProcedureReturn : EndIf
        s = RelativePos(*E, Start)
        If Length = -1
            e = ListSize(*E\Chars()) - 1
        Else
            e = RelativePos(*E, Start+Length-1)
        EndIf
        
        If s = -1 Or e = -1 : ProcedureReturn : EndIf
        
        Img = CreateImage(#PB_Any, 1, 1)
        If Img 
            If StartDrawing(ImageOutput(Img))
                DrawingFont(*E\Styles(Style)\Font_Id)
                
                If SelectElement(*E\Chars(), s)
                    PushListPosition(*E\Chars())
                    L = StartOfCurrentLine(*E)
                    PopListPosition(*E\Chars())
                    Repeat
                        If ListIndex(*E\Chars()) > e : Break : EndIf
                        *E\Chars()\S = Style
                        *E\Chars()\H = TextHeight( Chr( *E\Chars()\C ) )
                        *E\Chars()\W = TextWidth(  Chr( *E\Chars()\C ) )
                    Until NextElement(*E\Chars()) = 0
                EndIf
                StopDrawing()
            EndIf
            FreeImage(Img)
        EndIf
        
        BuildRowsOfLine(*E, L)
        DrawEditor(*E)
        
    EndProcedure
    Procedure.i UseStyle(Editor, Style)
        Protected   *E.TEditor = Editor
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        If Style >= 0 And Style <= ArraySize(*E\Styles()) : *E\UserStyle = Style : EndIf
    EndProcedure
    
    Procedure.s CloseEditor(Editor)
        ; close the editor and return its content
        Protected   *E.TEditor = Editor
        Protected   ret.s
        
        If Editors(Str(*E)) = 0 : ProcedureReturn : EndIf
        ret = GetText(*E)
        ; restore origin in parent canvas to (0,0)
        If StartDrawing(CanvasOutput(*E\Gadget))
            SetOrigin(0, 0)
            StopDrawing()
        EndIf
        RemoveCaratTimer(*E)
        SetGadgetAttribute(*E\Gadget, #PB_Canvas_Cursor, *E\Cursor)
        FreeStructure(*E)
        DeleteMapElement(Editors(), Str(Editor))
        ProcedureReturn ret
    EndProcedure
    
    Procedure.i OpenEditor(WinNbr, Gadget, Text.s, X=#PB_Default, Y=#PB_Default, W=#PB_Default, H=#PB_Default, FontNbr = -1, LineSep.s = #LF$)
        ; return an Editor (a raw pointer to TEditor)
        Protected *E.TEditor
        
        If IsGadget(Gadget) And GadgetType(Gadget) = #PB_GadgetType_Canvas
            If X = #PB_Default : X = 0                      : EndIf
            If Y = #PB_Default : Y = 0                      : EndIf
            If W = #PB_Default : W = GadgetWidth(Gadget)    : EndIf
            If H = #PB_Default : H = GadgetHeight(Gadget)   : EndIf
        Else
            MessageRequester("Error","Gadget needs to be an existing canvas gadget!")
            ProcedureReturn 0
        EndIf
        
        *E              = AllocateStructure(TEditor)
        Editors(Str(*E))= 1
        AddCaratTimer(WinNbr)           ; adds a carat-timer if not added to that window already
        
        *E\Window       = WinNbr
        *E\Gadget       = Gadget
        *E\X            = X
        *E\Y            = Y
        *E\W            = W 
        *E\H            = H
        *E\LineSeparator= LineSep
        
        *E\MarginTop    = 2
        *E\MarginDown   = 2
        *E\MarginLeft   = 1
        *E\MarginRight  = 1
        
        *E\BkgColor     = RGB(255,255,255)
        *E\SelBColor    = RGB(30, 144, 255)
        *E\SelFColor    = *E\BkgColor
        *E\CaratColor   = RGB(255, 0, 0)
        ;*E\Align        = #PB_Text_Center
        *E\LineSpacing  = 0
        
        If *E\Align <> #PB_Text_Right And *E\Align <> #PB_Text_Center : *E\Align = 0 : EndIf
        
        *E\Cursor = GetGadgetAttribute(Gadget, #PB_Canvas_Cursor)
        SetGadgetAttribute(Gadget, #PB_Canvas_Cursor, #PB_Cursor_IBeam)
        
        AddStyle(*E, FontNbr, RGB(0, 0, 139))       ; adding default style
        
        SetText(*E, Text)
        If FirstElement(*E\Chars()) : *E\TokenCarat = @*E\Chars() : EndIf
        *E\UserStyle = -1
        
        ResetSelection(*E)
        DrawEditor(*E)
        
        ProcedureReturn *E

    EndProcedure
    
EndModule

Last edited by said on Tue Sep 08, 2015 3:18 am, edited 3 times in total.
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: MyEditor Canvas based editor gadget

Post by said »

Here is one example ... i had to split it from above :!: you can copy both posts into one file!

Code: Select all

;- Example
;{

    EnableExplicit
    
    Global Window_0
    Global Canvas_0, Editor_0, Button_0
    
    Declare ResizeGadgetsWindow_0()
    
    Procedure OpenWindow_0(x = 0, y = 0, width = 690, height = 470)
      Window_0 = OpenWindow(#PB_Any, x, y, width, height, "MyEditor Gadget", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget)
      Canvas_0 = CanvasGadget(#PB_Any, 10, 10, 670, 350, #PB_Canvas_Keyboard)
      Editor_0 = EditorGadget(#PB_Any, 10, 390, 670, 70)
      Button_0 = ButtonGadget(#PB_Any, 10, 360, 130, 30, "Close Editor")
    EndProcedure
    Procedure ResizeGadgetsWindow_0()
      Protected FormWindowWidth, FormWindowHeight
      FormWindowWidth = WindowWidth(Window_0)
      FormWindowHeight = WindowHeight(Window_0)
      ResizeGadget(Canvas_0, 10, 10, FormWindowWidth - 20, FormWindowHeight - 120)
      ResizeGadget(Editor_0, 10, FormWindowHeight - 80, FormWindowWidth - 20, 70)
      ResizeGadget(Button_0, 10, FormWindowHeight - 110, 130, 30)
    EndProcedure
    
    Global Font_0, Font_1, Font_2, Font_3
    Global Txt_1.s, txt.s, Editor, style, i
    Global sep.s = #CRLF$ ; #LF$
    
    ;Txt_1 + "x" + sep + sep
    Txt_1 + sep + sep
    Txt_1 + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + sep + sep
    Txt_1 + "PureBasic is a high-level programming language based on established BASIC rules. It is mostly compatible with any other BASIC compiler, whether it's for the Amiga or PC format. Learning PureBasic is very easy! PureBasic has been created for beginners and experts alike. Compilation time is extremely fast. This software has been developed for the Windows operating system. We have put a lot of effort into its realization to produce a fast, reliable and system-friendly language. " + sep
    Txt_1 + "The syntax is easy and the possibilities are huge with the advanced functions that have been added to this language like pointers, structures, procedures, dynamic lists and much more. For the experienced coder, there are no problems gaining access to any of the legal OS structures or Windows API objects. " + sep
    Txt_1 + "PureBasic is a portable programming language which currently works on AmigaOS, Linux, MacOS X and Windows computer systems. This means that the same code can be compiled natively for the OS and use the full power of each. There are no bottlenecks like a virtual machine or a code translator, the generated code produces an optimized executable. " + sep
    Txt_1 + "The main features of PureBasic" + sep
    Txt_1 + "- x86, x64, 680x0 and PowerPC support" + sep 
    Txt_1 + "- Built-in arrays, dynamic lists, complex structures, pointers and variable definitions"  + sep
    Txt_1 + "- Supported types: Byte (8-bit), Word (16-bit), Long (32-bit), Quad (64-bit), Float (32-bit), Double (64-bit) and Characters"  + sep
    Txt_1 + "- User defined types (structures)"  + sep
    Txt_1 + "- Built-in string types (characters), including ascii and unicode"  ;+ sep
    Txt_1 + " 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + sep
    Txt_1 + "- Powerful macro support"  + sep
    Txt_1 + "- Constants, binary and hexadecimal numbers supported " + sep
    Txt_1 + "- Expression reducer by grouping constants and numeric numbers together " + sep
    Txt_1 + "- Standard arithmetic support in respect of sign priority and parenthesis: +, -, /, *, and, or, <<, >> " + sep
    Txt_1 + "- Extremely fast compilation " + sep
    Txt_1 + "- Procedure support for structured programming with local and global variables " + sep
    Txt_1 + "- All Standard BASIC keywords: If-Else-EndIf, Repeat-Until, etc " + sep
    Txt_1 + "- Specialized libraries to manipulate BMP pictures, windows, gadgets, DirectX, etc " + sep
    Txt_1 + "- Specialized libraries are very optimized for maximum speed and compactness " + sep
    Txt_1 + "- The Win32 API is fully supported as if they were BASIC keywords " + sep
    Txt_1 + "- Inline Assembler " + sep + sep
    Txt_1 + "- Precompiled structures with constants files for extra-fast compilation " + sep
    Txt_1 + "- Configurable CLI compiler " + sep
    Txt_1 + "- Very high productivity, comprehensive keywords, online help " + sep
    Txt_1 + "- System friendly, easy to install and easy to use " + sep + sep
    
    
    ;Txt_1 = "a " + sep + " b"
    ;Txt_1 = "a" +#TAB$+ "bb" +#TAB$+ "ccc" +#TAB$+ "dddd" +#TAB$+ "eeeee" +#TAB$+ "ffffff"
    ;Txt_1 = sep + "a"
    ;Txt_1 = sep + "a" + sep
    ;Txt_1 = "- System friendly, easy to install and easy to use " + sep + sep
    ;Txt_1 = sep + "x" + sep + "a" + sep + sep
    ;Txt_1 = "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
    ;Txt_1 = "a " + "012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
    
    ;Txt_1 = ReverseString(Txt_1)
    ;Txt_1 = "a " + "PureBasic is a high-level programming language based on established BASIC rules. It is mostly compatible with any other BASIC compiler" + sep
    
    ;Txt_1 = "Hello said"
    
    OpenWindow_0()
    
    Font_0 = LoadFont(#PB_Any, "Arial", 10)
    Font_1 = LoadFont(#PB_Any, "Courier New", 14 , #PB_Font_Bold|#PB_Font_Italic|#PB_Font_HighQuality)
    Font_2 = LoadFont(#PB_Any, "Tahoma", 12 , #PB_Font_Bold|#PB_Font_HighQuality)
    Font_3 = LoadFont(#PB_Any, "Impact", 18 , #PB_Font_HighQuality)
    
    Editor = MyEditor::OpenEditor(Window_0, Canvas_0, Txt_1, 0, 0, GadgetWidth(Canvas_0), GadgetHeight(Canvas_0), Font_0, sep)
    
    MyEditor::AddStyle(Editor, Font_1, RGB(255, 215, 0))    ; style # 1
    MyEditor::AddStyle(Editor, Font_2, RGB(205, 38, 38))    ; style # 2
    MyEditor::AddStyle(Editor, Font_3, RGB(0, 250, 154))    ; style # 3
    
    MyEditor::NoRedraw(Editor)
    i = 0
    Repeat
        style = Random(3, 1)
        i = FindString(Txt_1, "purebasic", i+1, #PB_String_NoCase)
        MyEditor::AssignStyle(Editor, style, i, 9)
    Until i = 0
    
    i = FindString(Txt_1, "purebasic", 1, #PB_String_NoCase)
    MyEditor::SelectText(Editor, style, i, 9)                         ; highlight the first purebasic word
    
    ;MyEditor::SetCaratPos(Editor, MyEditor::#MyEditor_LastCaratPos)     ; moves the carat to last pos
    MyEditor::Redraw(Editor)
    
    Repeat
        Select WaitWindowEvent()
            Case #PB_Event_SizeWindow
                ResizeGadgetsWindow_0()
                MyEditor::Resize(Editor, #PB_Ignore, #PB_Ignore, GadgetWidth(Canvas_0), GadgetHeight(Canvas_0))
                
            Case #PB_Event_Gadget
                
                Select EventGadget()
                    Case Canvas_0
                        MyEditor::ManageEvent(Editor, EventType())
                    Case Button_0
                        txt = MyEditor::CloseEditor(Editor)
                        SetGadgetText(Editor_0, txt)
                EndSelect
                
            Case #PB_Event_CloseWindow
                Break
                
        EndSelect
    ForEver
    

;}
Last edited by said on Tue Sep 08, 2015 3:16 am, edited 2 times in total.
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: MyEditor Canvas based editor gadget

Post by Kukulkan »

I'm running 5.24 LTS on Linux. It tells me that SetOrigin() is not a know function?
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: MyEditor Canvas based editor gadget

Post by said »

Kukulkan wrote:I'm running 5.24 LTS on Linux. It tells me that SetOrigin() is not a know function?
You are right! .. My mistake. SetOrigin() was introduced recently in LTS ... for LTS you need to use the latest one 5.4 LTS or 5.30 +
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: MyEditor Canvas based editor gadget

Post by davido »

@said,

Very nice, indeed. Thank you for sharing. :D
DE AA EB
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: MyEditor Canvas based editor gadget

Post by said »

davido wrote:@said,

Very nice, indeed. Thank you for sharing. :D
You are welcome davido

PS: I need to remove the code for few days, i will put back soon inshAllah, you can pm me if you need it
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: MyEditor Canvas based editor gadget

Post by said »

I re-uploaded the module and example (first 2 posts updated)

Now it is using a timer (no more threads!) and got the chance to fix few bugs!

Said
User avatar
flaith
Enthusiast
Enthusiast
Posts: 704
Joined: Mon Apr 25, 2005 9:28 pm
Location: $300:20 58 FC 60 - Rennes
Contact:

Re: MyEditor Canvas based editor gadget

Post by flaith »

Really nice, thanks Said :)
“Fear is a reaction. Courage is a decision.” - WC
User avatar
idle
Always Here
Always Here
Posts: 5098
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: MyEditor Canvas based editor gadget

Post by idle »

seems to work ok on linux in 5.40b3
Windows 11, Manjaro, Raspberry Pi OS
Image
Nico
Enthusiast
Enthusiast
Posts: 274
Joined: Sun Jan 11, 2004 11:34 am
Location: France

Re: MyEditor Canvas based editor gadget

Post by Nico »

Impressive and very useful, thank you for sharing. :D
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5357
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: MyEditor Canvas based editor gadget

Post by Kwai chang caine »

Nice thanks a lot said 8)
ImageThe happiness is a road...
Not a destination
collectordave
Addict
Addict
Posts: 1309
Joined: Fri Aug 28, 2015 6:10 pm
Location: Portugal

Re: MyEditor Canvas based editor gadget

Post by collectordave »

Excellent work.

After a couple of days added cut, copy and paste and change style of selected items.

Just adding a save and load routine to save formatting as well as text.

CD
Any intelligent fool can make things bigger and more complex. It takes a touch of genius — and a lot of courage to move in the opposite direction.
User avatar
Thorsten1867
Addict
Addict
Posts: 1366
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: MyEditor Canvas based editor gadget

Post by Thorsten1867 »

If you're looking for ideas, take a look at my gadget.
Creating an editor gadget is a real nerve-wracking job, especially cursor handling and marking with cursors.

viewtopic.php?f=27&t=71774
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
User avatar
Thorsten1867
Addict
Addict
Posts: 1366
Joined: Wed Aug 24, 2005 4:02 pm
Location: Germany

Re: MyEditor Canvas based editor gadget

Post by Thorsten1867 »

You could also add the marking of words or spaces by double-clicking on them.
Translated with http://www.DeepL.com/Translator

Download of PureBasic - Modules
Download of PureBasic - Programs

[Windows 11 x64] [PB V5.7x]
Joris
Addict
Addict
Posts: 885
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: MyEditor Canvas based editor gadget

Post by Joris »

Nice. Good work.
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
Post Reply