; _______________________________________________________________________________________
;|                                                                                       |
;| ..::FreshLib::..  Free, open source. Licensed under "BSD 2-clause" license."          |
;|_______________________________________________________________________________________|
;
;  Description: TEdit object class
;
;  Target OS: Any
;
;  Dependencies:
;
;  Notes: Represents single line edit control.
;_________________________________________________________________________________________
module "TEdit library"


object TEdit, TWindow
  ._Text  dd ?
  ._Len   dd ?  ; the length of the string in characters. (UTF-8)
  ._Start dd ?  ; Where the string begins to be displayed in the edit window.
  ._Pos   dd ?  ; Position of the caret in the string.
  ._Sel   dd ?  ; Position of the selction in the string.

  ._drag_mode    dd ?

  ._MarginLeft   dd ?
  ._MarginRight  dd ?

  ._Lock         dd ?

  param .Text, .GetText, .SetText
  param .Selection, .GetSel, .ReplaceSel

  param .MarginLeft,  ._MarginLeft, ._MarginLeft
  param .MarginRight, ._MarginRight, ._MarginRight

  param .CaretPos, ._Pos, .SetCaretPos

  method .Create, .parent
  method .Destroy

  method .GetText
  method .SetText, .value
  method .SetCaretPos, .value

  method .HitTest, .x, .select

; system event methods

  method .UpdateCaretPos

  method .GetSel
  method .ReplaceSel, .hString

  method .Paint

  method .EventKeyPress, .utf8, .scancode, .kbdState
  method .EventKeyRelease, .scancode, .kbdState

  method .EventButtonPress, .button, .kbdState, .x, .y
  method .EventMouseMove, .x, .y, .kbdState
  method .EventButtonRelease, .button, .kbdState, .x, .y

  method .EventFocusIn
  method .EventFocusOut

endobj



method TEdit.Create
begin
        push    ecx eax

        inherited [.parent]

        stdcall StrNew
        mov     ecx, [.self]
        mov     [ecx+TEdit._Text], eax
        mov     [ecx+TEdit._cursor], mcText
        mov     [ecx+TEdit.__want_focus], TRUE

        pop     eax ecx
        return
endp



method TEdit.Destroy
begin
        mov     eax, [.self]
        stdcall StrDel, [eax+TEdit._Text]
        return
endp




method TEdit.GetText
begin
        mov     eax, [.self]
        stdcall StrDup, [eax+TEdit._Text]
        return
endp



method TEdit.GetSel
begin
        pushad
        mov     esi, [.self]

        xor     edx, edx
        mov     ebx, [esi+TEdit._Pos]
        mov     ecx, [esi+TEdit._Sel]

        cmp     ebx, [esi+TEdit._Sel]
        je      .finish

        jl      @f
        xchg    ebx, ecx        ; ebx=beg_selection; ecx=end_selection
@@:
        stdcall StrOffsUtf8, [esi+TEdit._Text], ecx
        mov     ecx, eax

        stdcall StrOffsUtf8, [esi+TEdit._Text], ebx
        mov     ebx, eax

        stdcall StrPtr, [esi+TEdit._Text]
        sub     ebx, eax     ; begin of the selection
        sub     ecx, eax
        sub     ecx, ebx

        stdcall StrNew
        mov     edx, eax

        stdcall StrCopyPart, edx, [esi+TEdit._Text], ebx, ecx

.finish:
        mov     [esp+4*regEAX], edx
        popad
        return
endp


; returns CF=1 if there was no selection.
;         CF=0 if some text was deleted.

method TEdit.ReplaceSel ;, .hString
.beg   dd ?
.end   dd ?
.caret dd ?
begin
        pushad
        mov     esi, [.self]

        xor     edx, edx

        stdcall CaretShow, 0
        mov     [.caret], eax

        mov     ebx, [esi+TEdit._Pos]
        mov     ecx, [esi+TEdit._Sel]

        cmp     ebx, [esi+TEdit._Sel]
        jne     @f
        lea     edx, [edx+1]            ; ret CF=1
@@:
        jle     @f
        xchg    ebx, ecx        ; ebx=beg_selection; ecx=end_selection
@@:
        add     [esi+TEdit._Len], ebx
        sub     [esi+TEdit._Len], ecx

        stdcall StrOffsUtf8, [esi+TEdit._Text], ecx
        mov     [.end], eax

        stdcall StrOffsUtf8, [esi+TEdit._Text], ebx
        mov     [.beg], eax

        stdcall StrPtr, [esi+TEdit._Text]
        sub     [.beg], eax     ; begin of the selection
        sub     [.end], eax


        stdcall StrSplit, [esi+TEdit._Text], [.end]     ; end of the string
        mov     ecx, eax

        stdcall StrSplit, [esi+TEdit._Text], [.beg]
        stdcall StrDel, eax

        cmp     [.hString], 0
        je      .string_ok

        stdcall StrCat, [esi+TEdit._Text], [.hString]
        stdcall StrLenUtf8, [.hString], -1

        add     ebx, eax
        add     [esi+TEdit._Len], eax

.string_ok:
        mov     [esi+TEdit._Sel], ebx
        mov     [esi+TEdit._Pos], ebx

        stdcall StrCat, [esi+TEdit._Text], ecx
        stdcall StrDel, ecx

        cmp     [esi+TEdit._Lock], 0
        jne     .refresh_ok

        exec    esi, TEdit:Refresh

.refresh_ok:
        stdcall CaretShow, [.caret]

        shr     edx, 1          ; CF = return value
        popad
        return
endp





method TEdit.SetText
begin
        push    esi eax
        mov     esi, [.self]

        mov     [esi+TEdit._Start], 0
        mov     [esi+TEdit._Pos], 0
        mov     [esi+TEdit._Sel], 0

        lea     eax, [esi+TEdit._Text]
        stdcall SetString, eax, [.value]

        stdcall StrLenUtf8, [esi+TEdit._Text], -1
        mov     [esi+TEdit._Len], eax

        exec    esi, TEdit:Refresh
        pop     eax esi
        return
endp



method TEdit.Paint
 .bounds  TBounds
 .selbeg  dd ?
 .selend  dd ?

 .clBack  dd ?
 .border  dd ?
 .caret   dd ?

begin
        pushad

        mov     esi, [.self]
        cmp     [esi+TWindow._canvas], 0
        je      .finish

        stdcall CaretShow, FALSE
        mov     [.caret], eax

        mov     eax, [GUI.clEditBk]
        mov     ecx, [GUI.editBorder]

        cmp     esi, [__FocusedWindow]
        jne     @f
        mov     eax, [GUI.clEditBkFocused]
        mov     ecx, [GUI.editBorderFocused]
@@:
        mov     [.clBack], eax
        mov     [.border], ecx

; selection sort positions.
        mov     eax, [esi+TEdit._Pos]
        mov     ecx, [esi+TEdit._Sel]
        cmp     eax,ecx
        jle     @f
        xchg    eax, ecx
@@:
        mov     [.selbeg], eax
        mov     [.selend], ecx

; drawing bounds
        mov     ecx, [esi+TEdit._width]
        mov     eax, [esi+TEdit._height]

        mov     [.bounds.x], 0
        mov     [.bounds.y], 0
        mov     [.bounds.width], ecx
        mov     [.bounds.height], eax

        lea     eax, [.bounds]

        stdcall [DrawBox], [esi+TWindow._canvas], eax, [.clBack], [.border], [GUI.boxBorderWidth]

        mov     eax, [GUI.boxBorderWidth]
        add     [.bounds.x], eax
        add     [.bounds.y], eax
        shl     eax, 1
        sub     [.bounds.width], eax
        sub     [.bounds.height], eax

        mov     eax, [esi+TEdit._MarginLeft]
        mov     ecx, [esi+TEdit._MarginRight]
        add     [.bounds.x], eax
        sub     [.bounds.width], eax
        sub     [.bounds.width], ecx
        cmp     [.bounds.width], 0
        jle     .enddraw

        stdcall StrOffsUtf8, [esi+TEdit._Text], [esi+TEdit._Start]
        mov     edi, eax

        stdcall DrawTextBox, [esi+TEdit._canvas], edi, [.bounds.x], [.bounds.y], [.bounds.width], [.bounds.height], 0, dtfAlignLeft or dtfAlignMiddle or dtfSingleLine, [GUI.DefaultFont], [GUI.clEditTxt]

; determine the selection

        mov     eax, [.selbeg]
        xor     ecx, ecx

        sub     eax, [esi+TEdit._Start]
        jle     .xsel_ok

        stdcall StrOffsUtf8, edi, eax
        sub     eax, edi

        stdcall GetTextBounds, edi, eax, [GUI.DefaultFont]
        mov     ecx, eax

.xsel_ok:
        mov     eax, [.selend]
        sub     eax, [esi+TEdit._Start]
        jle     .enddraw

        stdcall StrOffsUtf8, edi, eax
        sub     eax, edi

        stdcall GetTextBounds, edi, eax, [GUI.DefaultFont]

        mov     edx, [.bounds.width]
        add     edx, [.bounds.x]

        cmp     eax, edx
        cmovg   eax, edx
        sub     eax, ecx

        add     ecx, [.bounds.x]
        stdcall BlendSolidRect, [esi+TWindow._canvas], ecx, [.bounds.y], eax, [.bounds.height], [GUI.clEditSel]

.enddraw:
        inherited

        stdcall CaretShow, [.caret]

.finish:
        popad
        return
endp




method TEdit.EventKeyPress      ;, .utf8, .scancode, .kbdState
begin
        pushad

        mov     esi, [.self]
        mov     [esi+TEdit._Lock], 1

        get     ebx, esi, TEdit:CaretPos

        mov     eax, [.utf8]

        test    eax, eax
        jz      .no_char

        cmp     eax, $20
        jb      .ctrl_char

        cmp     eax, $7f
        je      .no_char


        stdcall StrNew
        stdcall StrCharCat, eax, [.utf8]
        push    eax

        exec    esi, TEdit:ReplaceSel, eax

        stdcall StrDel ; from the stack

        mov     ebx, [esi+TEdit._Pos]
        jmp     .set_pos

.ctrl_char:

        OutputValue "Ascii:", eax, 10, 4

        cmp     eax, 1          ; Ctrl+A
        je      .select_all

        cmp     eax, 3          ; Ctrl+C
        je      .copy

        cmp     eax, 22         ; Ctrl+V
        je      .paste

        jmp     .no_char

.select_all:

        mov     ebx, [esi+TEdit._Len]
        mov     [esi+TEdit._Sel], 0
        jmp     .set_pos


.copy:
        DebugMsg "Copy"

        exec    esi, TEdit:GetSel
        test    eax, eax
        jz      .finish

        stdcall ClipboardWrite, eax
        stdcall StrDel, eax
        jmp     .finish

.paste:
        DebugMsg "Paste"

        stdcall ClipboardRead
        test    eax, eax
        jz      .finish

        push    eax

        exec    esi, TEdit:ReplaceSel, eax
        mov     ebx, [esi+TEdit._Pos]

        stdcall StrDel
        jmp     .endmove


.no_char:
        mov     eax, [.scancode]

        cmp     eax, keyLeftNumpad
        je      .left
        cmp     eax, keyLeft
        je      .left

        cmp     eax, keyRightNumpad
        je      .right
        cmp     eax, keyRight
        je      .right

        cmp     eax, keyHomeNumpad
        je      .home
        cmp     eax, keyHome
        je      .home

        cmp     eax, keyEndNumpad
        je      .end
        cmp     eax, keyEnd
        je      .end

        cmp     eax, keyDelNumpad
        je      .del
        cmp     eax, keyDelete
        je      .del

        cmp     eax, keyBackSpace
        je      .backdel

        jmp     .finish

.left:
        cmp     ebx, 0
        jle     .endmove

        dec     ebx
        jmp     .endmove

.right:
        cmp     ebx, [esi+TEdit._Len]
        jae     .endmove

        inc     ebx
        jmp     .endmove

.home:
        xor     ebx, ebx
        jmp     .endmove


.end:
        mov     ebx, [esi+TEdit._Len]
        jmp     .endmove


.backdel:
        cmp     ebx, [esi+TEdit._Sel]
        jne     .pos_ok

        test    ebx, ebx
        jz      .pos_ok                ; can't delete more than the begning

        dec     ebx
        mov     [esi+TEdit._Pos], ebx
        jmp     .pos_ok


.del:
        cmp     ebx, [esi+TEdit._Sel]
        jne     .pos_ok

        cmp     ebx, [esi+TEdit._Len]
        jae     .endmove                ; can't delete past the end.

        inc     ebx
        mov     [esi+TEdit._Pos], ebx

.pos_ok:
        exec    esi, TEdit:ReplaceSel, 0
        mov     ebx, [esi+TEdit._Pos]

.endmove:
        test    [.kbdState], maskShift
        jnz     .set_pos

        mov     [esi+TEdit._Sel], ebx

.set_pos:
        mov     [esi+TEdit._Lock], 0
        set     esi, TEdit:CaretPos, ebx

        exec    esi, TEdit:Refresh

.finish:
        popad
        return
endp



method TEdit.EventKeyRelease  ;, .scancode, .kbdState
begin

        return
endp



method TEdit.SetCaretPos
begin
        pushad

        mov     ecx, [.self]
        mov     eax, [.value]
        mov     [ecx+TEdit._Pos], eax

        exec    ecx, TEdit:Paint
        exec    ecx, TEdit:UpdateCaretPos

        popad
        return
endp



method TEdit.UpdateCaretPos
.refresh dd ?
begin
        pushad
        mov     esi, [.self]

        xor     eax, eax
        mov     [.refresh], eax

        mov     eax, [esi+TEdit._Pos]
        cmp     eax, [esi+TEdit._Start]
        jge     .compute_x

; scroll the text
        and     eax, $fffffff8
        mov     [esi+TEdit._Start], eax
        inc     [.refresh]

.compute_x:
        stdcall StrPtr, [esi+TEdit._Text]
        mov     edi, eax

        stdcall StrOffsUtf8, edi, [esi+TEdit._Pos]
        mov     ecx, eax

        stdcall StrOffsUtf8, edi, [esi+TEdit._Start]
        sub     ecx, eax                           ; offset to char under cursor.
        js      .finish


.leftok:
        mov     edi, eax                           ; StrOffsUtf8 returns pointer to start of the text.

        mov     eax, ecx
        jecxz   @f

        stdcall GetTextBounds, edi, ecx, [GUI.DefaultFont]

@@:
        lea     ecx, [eax+1]                       ; x coordinate of the cursor.
        add     ecx, [esi+TEdit._MarginLeft]
        add     ecx, [GUI.boxBorderWidth]

        mov     eax, [esi+TEdit._width]
        sub     eax, [esi+TEdit._MarginRight]
        sub     eax, [GUI.boxBorderWidth]

        cmp     eax, ecx
        jg      .caretok

        mov     eax, [esi+TEdit._Start]
        add     eax, 8
        cmp     eax, [esi+TEdit._Len]
        jle     @f
        mov     eax, [esi+TEdit._Len]
@@:
        mov     [esi+TEdit._Start], eax
        inc     [.refresh]
        jmp     .compute_x


.caretok:
        cmp     [.refresh], 0
        je      .canvas_ok

        exec    esi, TEdit:Paint

.canvas_ok:
        mov     edx, [esi+TEdit._height]
        sub     edx, 4

;        add     ecx, [GUI.boxBorderWidth]
        stdcall CaretChange, ecx, 2, 1, edx

.finish:
        popad
        return
endp


method TEdit.EventButtonPress
begin
        inherited [.button], [.kbdState], [.x], [.y]

        push    ecx
        mov     ecx, [.self]
        mov     [ecx+TEdit._drag_mode], 1

        stdcall SetMouseCapture, ecx
        exec    [.self], TEdit:HitTest, [.x], FALSE

        pop     ecx
        return
endp


method TEdit.EventMouseMove
begin
        inherited [.x], [.y], [.kbdState]

        push    ecx
        mov     ecx, [.self]

        cmp     [ecx+TEdit._drag_mode], 0
        je      .finish

        exec    [.self], TEdit:HitTest, [.x], TRUE

.finish:
        pop     ecx
        return
endp


method TEdit.EventButtonRelease
begin
        inherited  [.button], [.kbdState], [.x], [.y]
        push    ecx

        mov     ecx, [.self]
        mov     [ecx+TEdit._drag_mode], 0
        stdcall SetMouseCapture, 0
        pop     ecx
        return
endp




method TEdit.HitTest
.target dd ?
.start  dd ?
.offset dd ?
begin
        pushad
        mov     ebx, [.self]

        mov     ecx, [.x]
        sub     ecx, [GUI.boxBorderWidth]
        sub     ecx, [ebx+TEdit._MarginLeft]

        mov     [.target], ecx

        stdcall StrPtr, [ebx+TEdit._Text]
        mov     [.start], eax

        stdcall StrOffsUtf8, [.start], [ebx+TEdit._Start]
        sub     eax, [.start]

        stdcall GetTextBounds, [.start], eax, [GUI.DefaultFont]
        mov     [.offset], eax                        ; the offset in pixels of the leftmost edge of the window.


; Binary search of the position.

        mov     edi, [ebx+TEdit._Len]     ; current
        mov     esi, [ebx+TEdit._Len]     ; upper limit
        xor     ecx, ecx                  ; lower limit

.search_loop:
        stdcall StrOffsUtf8, [ebx+TEdit._Text], edi
        sub     eax, [.start]

        stdcall GetTextBounds, [.start], eax, [GUI.DefaultFont]
        sub     eax, [.offset]

        cmp     eax, [.target]
        jg      .go_down

; go up
        cmp     edi, esi
        je      .found

        mov     ecx, edi
        add     edi, esi
        sar     edi, 1

        cmp     edi, ecx
        je      .found
        jmp     .search_loop


.go_down:
        cmp     edi, ecx
        je      .found

        mov     esi, edi
        add     edi, ecx
        sar     edi, 1

        cmp     edi, esi
        je      .found
        jmp     .search_loop


.found:
        mov     eax, [ebx+TEdit._Start]
        add     eax, edi

        cmp     [.select], 0
        jne     @f
        mov     [ebx+TEdit._Sel], edi
@@:
        set     ebx, TEdit:CaretPos, edi
        exec    ebx, TEdit:Refresh

.finish:
        popad
        return

endp





method TEdit.EventFocusIn
begin
        stdcall CaretAttach, [.self]
        stdcall CaretShow, TRUE
        exec    [.self], TEdit:UpdateCaretPos
        inherited
        return
endp


method TEdit.EventFocusOut
begin
        stdcall CaretShow, FALSE
        stdcall CaretAttach, 0
        inherited
        return
endp




endmodule


