'PB 9 .04
'LBEd.inc
'
'   Cell Edit ListView
'
'   unlimited text size
'   in-cell editing
'   pop-up multi-line editing window - F2
'
'   'update: 3/25/2010
'   multi-line editing
'       pressing F2 while editing a ListView item will activates nulti-line editing window
'           F2, Ctl-S - save multi-line editing contents
'           Escape - abort without saving
'
'   row/column numbers are one-based to match PB functions
'
'   1) need UDT, "LVEdT", for each Edit ListView
'       1a. needs to be Static or Global
'
'   2) need user supplied function to call
'       2a. needs to match template:
'           LVEdCallProc(ByVal hDlg As Long, ByVal Id As Long, ByVal message As Long, ByVal rowNo As Long, ByVal colNo As Long) As Long
'       2b. one user function can handle all ListViews
'               test hDlg, ID
'           OR - have different function for each ListView
'
'   3) setup ListView: LVEd_Setup( thisLVEdT, listviewHDlg, listviewID, CodePtr(MyCallProc) )
'
'   4) place macro in Dialog's callback function;
'           "McLVEd_DailogMessageCracker", above/below Select Case code block
'       4a. macro will handle any number of listviews
'       4b. if more than one Dialog
'           4b1. need macro in each Dialog's callback function that has an Edit ListView
'
'   5) that's it for setup...
'
'   6) user callback function
'       6a. function called with info, or to get permission
'       6b. UserCallProc(hDlg, Id, message, rowNo, colNo)
'               hDlg = ListView's dialog
'               ID = ListView's ID
'               message = one of the below messages
'               rowNo/colNo - affected row or column
'           %LVEdMsg_ColClick - column (colNo) has been clicked
'               OK to ignore
'           %LVEdMsg_ItemClick - item at rowNo/colNo clicked
'               OK to ignore
'           %LVEdMsg_OkEdit - rowNo/colNo clicked - return True to allow editing
'               might want to block editing RowID, Blob Column, etc...
'           %LVEdMsg_ItemChanged - rowNo/colNo changed by editing cell
'               update data source
'           %LVEdMsg_OkRowInsert - rowNo - return True to allow inserting blank row
'           %LVEdMsg_RowInserted - called after new row inserted, before editing starts
'               set default values in row OR ignore
'           %LVEdMsg_OkRowDelete - rowNo -return True to allow row to be deleted
'               update data source before returning True
'
'   7) Keys
'       Escape
'           abort editing - discard changes - exit editing mode
'       Return, CtrlArrowD, ArrowD
'           save changes - edit cell below
'       TabL, CtrlArrowL
'           save changes - edit cell to left (%LVEdWrapRows = True; wrap around when needed)
'       TabR, CtrlArrowR
'           save changes - edit cell to right (%LVEdWrapRows = True; wrap around when needed)
'       CtrlArrowU, ArrowU
'           save changes - edit cell above
'       CtrlEnd
'           edit last cell in row
'       CtrlHome
'           edit first cell in row
'       CtrlDelete
'           delete row if permission granted (%LVEdMsg_OkRowDelete)
'       Insert
'           insert blank row if permission granted (%LVEdMsg_OkRowInsert)
'       PageD, PageU
'           move Up/Down one view page
'
'   !!! %USEMACROS not supported !!!
'
'   public domain - use at your risk

#INCLUDE ONCE "WIN32API.INC"
#INCLUDE ONCE "COMMCTRL.INC"

%LVEdWrapRows = %FALSE          'row editing wraps around to next row if %TRUE
%LVEdMultiLineTextWrap = %FALSE 'multi-line editing TextBox wraps text if %TRUE

%LVEdCookie = -65148557
%LVEdUserIndex = 1
%LVEdEditID = 2001
%LVEdMultiEditTxtID = 3001
%LVEdMultiEditBtnOkID = 3002
%LVEdMultiEditBtnCancel = 3003
%LVEdMultiEditTabStop = 16
%LVEdMultiEditHotKey = %VK_F2
$LVEdMultiEditTitle = "F2 - Save"

%LVEdKey_CtrlArrowD = 1
%LVEdKey_CtrlArrowL = 2
%LVEdKey_CtrlArrowR = 3
%LVEdKey_CtrlArrowU = 4
%LVEdKey_CtrlDelete = 5
%LVEdKey_ArrowD     = 6
%LVEdKey_ArrowU     = 7
%LVEdKey_CtrlEnd    = 8
%LVEdKey_CtrlHome   = 9
%LVEdKey_End        = 10
%LVEdKey_Escape     = 11
%LVEdKey_Home       = 12
%LVEdKey_Insert     = 13
%LVEdKey_PageD      = 14
%LVEdKey_PageU      = 15
%LVEdKey_Return     = 16
%LVEdKey_TabL       = 17
%LVEdKey_TabR       = 18
%LVEdKey_MultiEdit  = 19

DECLARE FUNCTION LVEdCallProc(BYVAL hDlg AS LONG, BYVAL ID AS LONG, BYVAL message AS LONG, BYVAL rowNo AS LONG, BYVAL colNo AS LONG) AS LONG
'hDlg = ListView's dialog
'ID = ListView's control ID
'message = (see below)
%LVEdMsg_ColClick = 965996437     'colNo = column clicked
%LVEdMsg_ItemClick = 1292507146   'rowNo/colNo = item clicked
%LVEdMsg_OkEdit = 1971013811      'rowNo/colNo = item : return True to edit item
%LVEdMsg_ItemChanged = 732240118  'rowNo/colNo = item changed (update data)
%LVEdMsg_OkRowInsert = 1806805900 'rowNo = position : return True to allow row insertion
%LVEdMsg_RowInserted = 1762741735 'rowNo = new row : called before editing begins
%LVEdMsg_OkRowDelete = 1655139966 'rowNo = row to delete : return True to allow row delete (update data before row deleted)

TYPE LVEdT
    cookie AS LONG
    hDlg AS LONG
    ID AS LONG
    callProc AS LONG
    LV_hWin AS LONG
    LV_OldProc AS LONG
    Edit_Is AS BYTE
    Edit_hDlg AS LONG
    Edit_ID AS LONG
    Edit_hWin AS LONG
    Edit_OldProc AS LONG
    Edit_Row AS LONG
    Edit_Col AS LONG
    Edit_Rect AS RECT
    MEdit_hDlg AS LONG
    MEdit_TxtID AS LONG
    MEdit_TxtHWin AS LONG
    MEdit_TxtOldProc AS LONG
    MEdit_BtnOkID AS LONG
    MEdit_BtnCancelID AS LONG
    MEdit_ChkWrapID AS LONG
    MEdit_Left AS LONG
    MEdit_Top AS LONG
    MEdit_Width AS LONG
    MEdit_Height AS LONG
END TYPE

SUB LVEd_Setup(t AS LVEdT, BYVAL hDlg AS LONG, BYVAL ID AS LONG, BYVAL callProc AS DWORD)
    LOCAL sty AS LONG
    IF hDlg AND ID AND callProc THEN
        t.cookie = %LVEdCookie
        t.hDlg = hDlg
        t.ID = ID
        t.callProc = callProc
        CONTROL HANDLE hDlg, ID TO t.LV_hWin
        t.LV_OldProc = SetWindowLong(t.LV_hWin, %GWL_WNDPROC, CODEPTR(LVEd_ListViewSubclass))
        t.Edit_Is = %false
        t.Edit_hDlg = t.LV_hWin
        t.Edit_ID = %LVEdEditID
        t.Edit_hWin = %null
        t.Edit_OldProc = %null
        t.Edit_Row = %null
        t.Edit_Col = %null
        t.MEdit_hDlg = %null
        t.MEdit_TxtID = %LVEdMultiEditTxtID
        t.MEdit_TxtHWin = %null
        t.MEdit_TxtOldProc = %Null
        t.MEdit_BtnOkID = %LVEdMultiEditBtnOkID
        t.MEdit_BtnCancelID = %LVEdMultiEditBtnCancel
        t.MEdit_Left = 0
        t.MEdit_Top = 0
        t.MEdit_Width = 0
        t.MEdit_Height = 0
        CONTROL SET USER hDlg, ID, %LVEdUserIndex, VARPTR(t)
        sty = GetWindowLong(t.LV_hWin, %GWL_STYLE)
        sty OR = %LVS_SINGLESEL
        SetWindowLong(t.LV_hWin, %GWL_STYLE, sty)
        LISTVIEW SET MODE hDlg, ID, 1
        LISTVIEW GET STYLEXX hDlg, ID TO sty
        LISTVIEW SET STYLEXX hDlg, ID, sty OR %LVS_EX_GRIDLINES OR %LVS_EX_FULLROWSELECT OR %LVS_EX_ONECLICKACTIVATE OR %LVS_EX_INFOTIP OR %LVS_EX_DOUBLEBUFFER
    END IF
END SUB


'message cracker macro goes in ListView Dialog's callback function
'   above/below Select Case code block
MACRO McLVEd_DailogMessageCracker
    MACROTEMP p, result, pnmlv
    LOCAL p AS LVEdT PTR
    LOCAL result AS LONG
    LOCAL pnmlv AS NM_LISTVIEW PTR

    IF CB.HNDL AND CB.CTL THEN
        CONTROL GET USER CB.HNDL, CB.CTL, %LVEdUserIndex TO p
        IF p AND @p.cookie = %LVEdCookie THEN
            SELECT CASE AS LONG CB.MSG
            CASE %WM_NOTIFY
                pnmlv = CB.LPARAM
                SELECT CASE @pnmlv.hdr.code
                CASE %LVN_COLUMNCLICK 'column clicked
                    IF @p.Edit_Is THEN
                        LVEd_EditSave @p
                        LVEd_EditKill @p
                    END IF
                    CALL DWORD @p.callProc USING LVEdCallProc(CB.HNDL, CB.CTL, %LVEdMsg_ColClick, %null, @pnmlv.iSubItem + 1)
                    FUNCTION = %true : EXIT FUNCTION
                CASE %NM_CLICK', %NM_DblClk  'item clicked
                    IF @p.Edit_Is THEN
                        LVEd_EditSave @p
                        LVEd_EditKill @p
                    END IF
                    CALL DWORD @p.callProc USING LVEdCallProc(CB.HNDL, CB.CTL, %LVEdMsg_ItemClick, @pnmlv.iItem + 1, @pnmlv.iSubItem + 1)
                    CALL DWORD @p.callProc USING LVEdCallProc(CB.HNDL, CB.CTL, %LVEdMsg_OkEdit, @pnmlv.iItem + 1, @pnmlv.iSubItem + 1) TO result
                    IF result THEN
                        @p.Edit_Row = @pnmlv.iItem + 1
                        @p.Edit_Col = @pnmlv.iSubItem + 1
                        LVEd_EditItem @p
                    END IF
                    FUNCTION = %true : EXIT FUNCTION
                CASE %NM_SETFOCUS
                    IF @p.Edit_Is THEN
                        LVEd_EditSave @p
                        LVEd_EditKill @p
                    END IF
                END SELECT
            CASE %WM_DESTROY
                IF @p.Edit_Is THEN
                    LVEd_EditSave @p
                    LVEd_EditKill @p
                END IF
            END SELECT
        END IF
    END IF
END MACRO

FUNCTION LVEd_GetText(BYVAL hDlg AS LONG, BYVAL ID AS LONG, BYVAL rowNo AS LONG, BYVAL colNo AS LONG) AS STRING
    'get text
    'no limit on text size
    '!!! one-based row, column !!!
    LOCAL myLen, returnLen AS LONG
    LOCAL s AS STRING
    LOCAL tLVItem AS LVITEM
    LOCAL hWin AS LONG

    hWin = GetDlgItem(hDlg,ID)
    myLen = 256 'could be bigger
    HERE:
    s = REPEAT$(myLen + 1, $NUL)
    tLVItem.iSubItem = colNo - 1
    tLVItem.cchTextMax = myLen
    tLVItem.pszText = STRPTR(s)
    returnLen = SendMessage(hWin, %LVM_GETITEMTEXT, rowNo - 1, VARPTR(tLVItem))
    'keep trying until returnLen is less than buffer size
    IF returnLen >= myLen - 1 THEN
        myLen += myLen
        GOTO HERE
    END IF

    FUNCTION = LEFT$(s, returnLen)
END FUNCTION

' ----------------------------------------------------------------
'   INTERNAL USE
' ----------------------------------------------------------------

CALLBACK FUNCTION LVEd_ListViewSubclass()
    LOCAL p AS LVEdT PTR

    DIALOG GET USER CB.HNDL, %LVEdUserIndex TO p

    SELECT CASE AS LONG CB.MSG
    CASE %WM_KEYDOWN
        SELECT CASE CB.WPARAM
        CASE %VK_PGUP '%LVEdKey_PageU
            LVEd_EditKey(@p, %LVEdKey_PageU) : EXIT FUNCTION
        CASE %VK_PGDN '%LVEdKey_PageD
            LVEd_EditKey(@p, %LVEdKey_PageD) : EXIT FUNCTION
        CASE %VK_DOWN '%LVEdKey_ArrowD
            LVEd_EditKey(@p, %LVEdKey_ArrowD) : EXIT FUNCTION
        CASE %VK_UP '%LVEdKey_ArrowU
            LVEd_EditKey(@p, %LVEdKey_ArrowU) : EXIT FUNCTION
        CASE %VK_HOME '%LVEdKey_Home
            LVEd_EditKey(@p, %LVEdKey_Home) : EXIT FUNCTION
        CASE %VK_END '%LVEdKey_End
            LVEd_EditKey(@p, %LVEdKey_End) : EXIT FUNCTION
        CASE %VK_INSERT '%LVEdKey_Insert
            LVEd_EditKey(@p, %LVEdKey_Insert) : EXIT FUNCTION
        CASE %VK_DELETE  '%LVEdKey_CtrlDelete
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlDelete) : EXIT FUNCTION
            END IF
        END SELECT
    CASE %WM_VSCROLL, %WM_HSCROLL
        IF @p.Edit_Is THEN
            LVEd_EditSave @p
            LVEd_EditKill @p
        END IF
    CASE %WM_DESTROY
        IF @p.Edit_Is THEN
            LVEd_EditSave @p
            LVEd_EditKill @p
        END IF
        SetWindowLong CB.HNDL, %GWL_WNDPROC, @p.LV_OldProc
    END SELECT

    FUNCTION = CallWindowProc(@p.LV_OldProc, CB.HNDL, CB.MSG, CB.WPARAM, CB.LPARAM)
END FUNCTION

SUB LVEd_EditItem(t AS LVEdT)
    LOCAL rowCount, colCount AS LONG
    '
    LISTVIEW GET COUNT t.hDlg, t.ID TO rowCount
    colCount = LVEd_ColCount(t)
    IF t.Edit_Row > 0 AND t.Edit_Row <= rowCount AND t.Edit_Col = 3 AND t.Edit_Col <= colCount THEN       ' Alleen de 3e Column is te wijzigen!!!
'    IF t.Edit_Row > 0 AND t.Edit_Row <= rowCount AND t.Edit_Col > 0 AND t.Edit_Col <= colCount THEN
        t.Edit_Is = %true
        LISTVIEW SELECT t.hDlg, t.ID, t.Edit_Row
        LVEd_EditVisible t
        CONTROL ADD TEXTBOX, t.Edit_hDlg, t.Edit_ID, "", 0, 0, 0, 0, %WS_CHILD OR %WS_TABSTOP OR %ES_LEFT OR %ES_AUTOHSCROLL OR %DS_SETFOREGROUND
        'set limit before adding text
        CONTROL SEND t.Edit_hDlg, t.Edit_ID, %EM_SETLIMITTEXT, 0, 0
        CONTROL SET TEXT t.Edit_hDlg, t.Edit_ID, LVEd_GetText(t.hDlg, t.ID, t.Edit_Row, t.Edit_Col)
        CONTROL HANDLE t.Edit_hDlg, t.Edit_ID TO t.Edit_hWin
        LVEd_EditRect t
        SetWindowPos(t.Edit_hWin, %HWND_TOP, t.Edit_Rect.nLeft, t.Edit_Rect.nTop, t.Edit_Rect.nRight - t.Edit_Rect.nLeft, t.Edit_Rect.nBottom - t.Edit_Rect.nTop, %SWP_SHOWWINDOW)
        SendMessage t.Edit_hWin, %WM_SETFONT, (SendMessage(t.LV_hWin,%WM_GETFONT,0,0)), 0
        CONTROL SET FOCUS t.Edit_hDlg, t.Edit_ID
        'select-all slow for large text contents
        'Control Send t.Edit_hDlg, t.Edit_ID, %EM_SETSEL, 0, -1
        t.Edit_OldProc = SetWindowLong(t.Edit_hWin, %GWL_WNDPROC, CODEPTR(LVEd_EditSubclass))
        DIALOG SET USER t.Edit_hWin, %LVEdUserIndex, VARPTR(t)
    END IF
END SUB

SUB LVEd_EditSave(t AS LVEdT)
    LOCAL modified AS LONG
    LOCAL s AS STRING
    IF t.Edit_Is THEN
        CONTROL SEND t.Edit_hDlg, t.Edit_ID, %EM_GETMODIFY, 0, 0 TO modified
        IF modified THEN
            CONTROL GET TEXT t.Edit_hDlg, t.Edit_ID TO s
            LISTVIEW SET TEXT t.hDlg, t.ID, t.Edit_Row, t.Edit_Col, s
            CONTROL SEND t.Edit_hDlg, t.Edit_ID, %EM_SETMODIFY, %false, 0
            CALL DWORD t.callProc USING LVEdCallProc(t.hDlg, t.ID, %LVEdMsg_ItemChanged, t.Edit_Row, t.Edit_Col)
        END IF
    END IF
END SUB

SUB LVEd_EditKill(t AS LVEdT)
    IF t.Edit_Is THEN
        t.Edit_Is = %false
        CONTROL KILL t.Edit_hDlg, t.Edit_ID
    END IF
END SUB

FUNCTION LVEd_ColCount(t AS LVEdT) AS LONG
    'get column count
    LOCAL hHead&
    hHead& = SendMessage(t.LV_hWin, %LVM_GETHEADER, 0, 0)
    FUNCTION = SendMessage(hHead&, %HDM_GETITEMCOUNT, 0, 0)
END FUNCTION

SUB LVEd_EditVisible(t AS LVEdT)
    'scroll edit row/col into view
    LOCAL r, c AS LONG
    LOCAL itemRect AS RECT
    LOCAL lvRect AS RECT
    '
    r = t.Edit_Row - 1
    c = t.Edit_Col - 1
    CONTROL SEND t.hDlg, t.ID, %LVM_ENSUREVISIBLE, r, 0
    ListView_GetSubItemRect(t.LV_hWin, r, c, %LVIR_BOUNDS, VARPTR(itemRect))
    IF itemRect.nLeft < 0 THEN
        ListView_Scroll t.LV_hWin, itemRect.nLeft -1, 0
    ELSEIF c > 0 THEN
        GetClientRect t.LV_hWin, lvRect
        IF itemRect.nRight > lvRect.nRight THEN
            ListView_Scroll t.LV_hWin, itemRect.nRight - lvRect.nRight + 1, 0
        END IF
    END IF
END SUB

SUB LVEd_EditRect(t AS LVEdT)
    LOCAL r, c, colCount AS LONG
    LOCAL rct2 AS RECT
    '
    r = t.Edit_Row - 1
    c = t.Edit_Col - 1
    Listview_GetSubItemRect t.LV_hWin, r, c, %LVIR_BOUNDS, BYVAL VARPTR(t.Edit_Rect)
    IF c = 0 AND LVEd_ColCount(t) <> 1 THEN
        Listview_GetSubItemRect t.LV_hWin, r, 1, %LVIR_BOUNDS, BYVAL VARPTR(rct2)
        t.Edit_Rect.nRight = rct2.nLeft-1
    END IF
END SUB

FUNCTION LVEd_RowsPerPage(t AS LVEdT) AS LONG
    'get row display count
    FUNCTION = SendMessage(t.LV_hWin, %LVM_GETCOUNTPERPAGE, 0, 0)
END FUNCTION

SUB LVEd_EditKey(t AS LVEdT, BYVAL key AS LONG)
    LOCAL r, c AS LONG
    LOCAL rowCount AS LONG
    LOCAL colCount AS LONG
    LOCAL result AS LONG

    r = t.Edit_Row
    c = t.Edit_Col
    LISTVIEW GET COUNT t.hDlg, t.ID TO rowCount
    colCount = LVEd_ColCount(t)
    IF colCount = 0 THEN EXIT SUB
    SELECT CASE AS LONG key
    CASE %LVEdKey_Escape
        LVEd_EditKill t
    CASE %LVEdKey_MultiEdit
        IF t.Edit_Is THEN
            LVEd_EditSave t
            LVEd_EditKill t
        END IF
        LVEd_MultiEditItem t
    CASE %LVEdKey_ArrowU, %LVEdKey_CtrlArrowU
        IF r > 1 THEN DECR r
        GOTO DO_EDIT
    CASE %LVEdKey_ArrowD, %LVEdKey_CtrlArrowD, %LVEdKey_Return
        IF r < rowCount THEN INCR r
        GOTO DO_EDIT
    CASE %LVEdKey_TabL, %LVEdKey_CtrlArrowL
        IF c > 1 THEN
            DECR c
        ELSEIF %LVEdWrapRows AND r > 1 THEN
            DECR r
            c = colCount
        END IF
        GOTO DO_EDIT
    CASE %LVEdKey_TabR, %LVEdKey_CtrlArrowR
        IF c < colCount THEN
            INCR c
        ELSEIF %LVEdWrapRows AND r < rowCount THEN
            INCR r
            c = 1
        END IF
        GOTO DO_EDIT
    CASE %LVEdKey_PageD
        r += LVEd_RowsPerPage(t)
        IF r > rowCount THEN r = rowCount
        GOTO DO_EDIT
    CASE %LVEdKey_PageU
        r -= LVEd_RowsPerPage(t)
        IF r < 1 THEN r = 1
        GOTO DO_EDIT
    CASE %LVEdKey_CtrlHome
        c = 1
        GOTO DO_EDIT
    CASE %LVEdKey_CtrlEnd
        c = colCount
        GOTO DO_EDIT
    CASE %LVEdKey_Insert
        IF t.Edit_Is THEN
            LVEd_EditSave t
            LVEd_EditKill t
        END IF
        IF rowCount = 0 THEN r = 1
        CALL DWORD t.callProc USING LVEdCallProc(t.hDlg, t.ID, %LVEdMsg_OkRowInsert, r, %null) TO result
        IF result THEN
            LISTVIEW INSERT ITEM t.hDlg, t.ID, r, %null, ""
            CALL DWORD t.callProc USING LVEdCallProc(t.hDlg, t.ID, %LVEdMsg_RowInserted, r, %null)
            c = 1
            CALL DWORD t.callProc USING LVEdCallProc(t.hDlg, t.ID, %LVEdMsg_OkEdit, r, c) TO result
            IF result THEN
                GOTO DO_EDIT
            END IF
        END IF
    CASE %LVEdKey_CtrlDelete
        IF rowCount THEN
            LVEd_EditKill t
            CALL DWORD t.callProc USING LVEdCallProc(t.hDlg, t.ID, %LVEdMsg_OkRowInsert, r, %null) TO result
            IF result THEN
                LISTVIEW DELETE ITEM t.hDlg, t.ID, r
                DECR rowCount
                IF r > rowCount THEN r = rowCount
                IF r THEN GOTO DO_EDIT
            END IF
        END IF
    END SELECT
    EXIT SUB
    DO_EDIT:
        IF t.Edit_Is THEN
            LVEd_EditSave t
            LVEd_EditKill t
        END IF
        CALL DWORD t.callProc USING LVEdCallProc(t.hDlg, t.ID, %LVEdMsg_OkEdit, r, c) TO result
        IF result THEN
            t.Edit_Row = r
            t.Edit_Col = c
            LVEd_EditItem t
        END IF
END SUB

CALLBACK FUNCTION LVEd_EditSubclass()
    LOCAL p AS LVEdT PTR

    DIALOG GET USER CB.HNDL, %LVEdUserIndex TO p

    SELECT CASE AS LONG CB.MSG
    CASE %WM_GETDLGCODE
        FUNCTION = %DLGC_WANTALLKEYS
        EXIT FUNCTION
    CASE %WM_CHAR
        IF (CB.WPARAM = %VK_RETURN) OR (CB.WPARAM = %VK_TAB) THEN
            EXIT FUNCTION
        END IF
    CASE %WM_KEYDOWN
        SELECT CASE CB.WPARAM
        CASE %LVEdMultiEditHotKey
            LVEd_EditKey(@p, %LVEdKey_MultiEdit) : EXIT FUNCTION
        CASE %VK_ESCAPE '%LVEdKey_Escape
            LVEd_EditKey(@p, %LVEdKey_Escape) : EXIT FUNCTION
        CASE %VK_RETURN '%LVEdKey_Return
            LVEd_EditKey(@p, %LVEdKey_Return) : EXIT FUNCTION
        CASE %VK_TAB '%LVEdKey_TabL, %LVEdKey_TabR
            IF (GetKeyState(%VK_SHIFT) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_TabL) : EXIT FUNCTION
            ELSE
                LVEd_EditKey(@p, %LVEdKey_TabR) : EXIT FUNCTION
            END IF
            EXIT FUNCTION
        CASE %VK_UP  '%LVEdKey_CtrlArrowU, %LVEdKey_ArrowU
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlArrowU) : EXIT FUNCTION
            ELSE
                 LVEd_EditKey(@p, %LVEdKey_ArrowU) : EXIT FUNCTION
            END IF
        CASE  %VK_DOWN '%LVEdKey_CtrlArrowD, %LVEdKey_ArrowD
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlArrowD) : EXIT FUNCTION
            ELSE
                LVEd_EditKey(@p, %LVEdKey_ArrowD) : EXIT FUNCTION
            END IF
        CASE %VK_PGUP '%LVEdKey_PageU
            LVEd_EditKey(@p, %LVEdKey_PageU) : EXIT FUNCTION
        CASE %VK_PGDN '%LVEdKey_PageD
            LVEd_EditKey(@p, %LVEdKey_PageD) : EXIT FUNCTION
        CASE %VK_HOME
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlHome) : EXIT FUNCTION
            END IF
        CASE %VK_END '%LVEdKey_CtrlEnd
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlEnd) : EXIT FUNCTION
            END IF
        CASE %VK_INSERT '%LVEdKey_Insert
            LVEd_EditKey(@p, %LVEdKey_Insert) : EXIT FUNCTION
        CASE %VK_LEFT  '%LVEdKey_CtrlArrowL
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlArrowL) : EXIT FUNCTION
            END IF
        CASE %VK_RIGHT  '%LVEdKey_CtrlArrowR
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlArrowR) : EXIT FUNCTION
            END IF
        CASE %VK_DELETE  '%LVEdKey_CtrlDelete
            IF (GetKeyState(%VK_CONTROL) AND &h8000) THEN
                LVEd_EditKey(@p, %LVEdKey_CtrlDelete) : EXIT FUNCTION
            END IF
        END SELECT
    CASE %WM_KILLFOCUS
        IF @p.Edit_Is THEN
            LVEd_EditSave @p
            LVEd_EditKill @p
        END IF
    CASE %WM_DESTROY
        SetWindowLong CB.HNDL, %GWL_WNDPROC, @p.Edit_OldProc
    END SELECT

    FUNCTION = CallWindowProc(@p.Edit_OldProc, CB.HNDL, CB.MSG, CB.WPARAM, CB.LPARAM)
END FUNCTION

SUB LVEd_MultiEditItem(t AS LVEdT)
    LOCAL sty, styX AS LONG
    LOCAL tabStop AS LONG
    LOCAL s AS STRING
    '
    IF t.Edit_Is THEN
        LVEd_EditSave t
        LVEd_EditKill t
    END IF
    '
    IF t.MEdit_Width = 0 THEN
        t.MEdit_Left = 0 : t.MEdit_Top = 0 : t.MEdit_Width = 200 : t.MEdit_Height = 125
    END IF
    DIALOG NEW t.hDlg, $LVEdMultiEditTitle, t.MEdit_Left, t.MEdit_Top, t.MEdit_Width, t.MEdit_Height, %WS_POPUP OR %WS_BORDER OR %WS_DLGFRAME OR %WS_THICKFRAME OR %WS_VISIBLE OR %DS_SETFOREGROUND OR %DS_3DLOOK _
    OR %DS_NOFAILCREATE OR %DS_SETFONT, %WS_EX_CONTROLPARENT  OR %WS_EX_TOOLWINDOW OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR %WS_EX_RIGHTSCROLLBAR, TO t.MEdit_hDlg
    '
    IF %LVEdMultiLineTextWrap THEN
        sty = %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP OR %WS_VSCROLL OR %ES_LEFT OR %ES_MULTILINE OR %ES_AUTOVSCROLL OR %ES_NOHIDESEL OR %ES_WANTRETURN
    ELSE
        sty = %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP OR %WS_VSCROLL OR %ES_LEFT OR %ES_MULTILINE OR %ES_AUTOVSCROLL OR %ES_NOHIDESEL OR %ES_WANTRETURN OR %ES_AUTOHSCROLL OR %WS_HSCROLL
    END IF
    styX = %WS_EX_CLIENTEDGE OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR %WS_EX_RIGHTSCROLLBAR
    CONTROL ADD TEXTBOX,  t.MEdit_hDlg, t.MEdit_TxtID, "", 0, 0, 0, 0, sty, styX
    CONTROL SEND t.MEdit_hDlg, t.MEdit_TxtID, %EM_SETLIMITTEXT, 0, 0
    tabStop = %LVEdMultiEditTabStop
    CONTROL SEND t.MEdit_hDlg, t.MEdit_TxtID, %EM_SETTABSTOPS, 1, VARPTR(tabStop)
    '
    CONTROL ADD BUTTON, t.MEdit_hDlg, t.MEdit_BtnOkID, "OK", 0, 0, 42, 14
    CONTROL ADD BUTTON, t.MEdit_hDlg, t.MEdit_BtnCancelID, "Cancel", 0, 0, 42, 14
    '
    CONTROL HANDLE  t.MEdit_hDlg, t.MEdit_TxtID TO t.MEdit_TxtHWin
    DIALOG SET USER t.MEdit_hDlg, %LVEdUserIndex, VARPTR(t)
    CONTROL SET USER t.MEdit_hDlg, t.MEdit_TxtID, %LVEdUserIndex, VARPTR(t)
    '
    DIALOG SHOW MODAL t.MEdit_hDlg, CALL LVEd_MultiEditProc()
END SUB

CALLBACK FUNCTION LVEd_MultiEditProc()
    STATIC hFont AS LONG
    LOCAL p AS LVEdT PTR
    LOCAL cw, ch AS LONG
    LOCAL btnTop AS LONG
    LOCAL s AS STRING

    DIALOG GET USER CB.HNDL, %LVEdUserIndex TO p

    SELECT CASE AS LONG CB.MSG
    CASE %WM_INITDIALOG
        @p.MEdit_TxtOldProc = SetWindowLong(@p.MEdit_TxtHWin, %GWL_WNDPROC, CODEPTR(LVEd_MultiEditSubclass))
        FONT NEW "courier new" ,9 , 0, 0, 0, 0 TO hFont
        CONTROL SET FONT CB.HNDL, @p.MEdit_TxtID, hFont
        CONTROL SET TEXT CB.HNDL, @p.MEdit_TxtID, LVEd_GetText(@p.hDlg, @p.ID, @p.Edit_Row, @p.Edit_Col)
        CONTROL SET FOCUS CB.HNDL, @p.MEdit_TxtID
    CASE %WM_NCACTIVATE
        STATIC hWndSaveFocus AS DWORD
        IF ISFALSE CB.WPARAM THEN
            hWndSaveFocus = GetFocus()
        ELSEIF hWndSaveFocus THEN
            SetFocus(hWndSaveFocus)
            hWndSaveFocus = 0
        END IF
    CASE %WM_SIZE
        DIALOG GET CLIENT CB.HNDL TO cw, ch
        @p.MEdit_Width = cw
        @p.MEdit_Height = ch
        @p.MEdit_Left = 0
        @p.MEdit_Top = 0
        btnTop = ch - 18
        CONTROL SET SIZE CB.HNDL, @p.MEdit_TxtID, cw, btnTop - 4
        CONTROL REDRAW CB.HNDL, @p.MEdit_TxtID
        CONTROL SET LOC CB.HNDL, @p.MEdit_BtnCancelID, cw - 46, btnTop
        CONTROL REDRAW CB.HNDL, @p.MEdit_BtnCancelID
        CONTROL SET LOC CB.HNDL, @p.MEdit_BtnOkID, cw - 92, btnTop
        CONTROL REDRAW CB.HNDL, @p.MEdit_BtnOkID
    CASE %WM_GETMINMAXINFO
        LOCAL MinMaxPtr AS MINMAXINFO PTR
        MinMaxPtr = CB.LPARAM
        @MinMaxPtr.ptMinTrackSize.x = 250
        @MinMaxPtr.ptMinTrackSize.y = 200
        @MinMaxPtr.ptMaxTrackSize.x = 3200
        @MinMaxPtr.ptMaxTrackSize.y = 3200
    CASE %WM_COMMAND
        SELECT CASE AS LONG CBCTL
        CASE @p.MEdit_BtnOkID
            IF CB.CTLMSG = %BN_CLICKED THEN
                CONTROL GET TEXT CB.HNDL, @p.MEdit_TxtID TO s
                LISTVIEW SET TEXT @p.hDlg, @p.ID, @p.Edit_Row, @p.Edit_Col, s
                CALL DWORD @p.callProc USING LVEdCallProc(@p.hDlg, @p.ID, %LVEdMsg_ItemChanged, @p.Edit_Row, @p.Edit_Col)
                DIALOG END CB.HNDL
            END IF
        CASE @p.MEdit_BtnCancelID
            IF CB.CTLMSG = %BN_CLICKED THEN
                DIALOG END CB.HNDL
            END IF
        END SELECT
    CASE %WM_DESTROY
        FONT END hFont
        SetWindowLong @p.MEdit_TxtHWin, %GWL_WNDPROC, @p.MEdit_TxtOldProc
        CONTROL SET FOCUS @p.hDlg, @p.ID
    END SELECT
END FUNCTION

CALLBACK FUNCTION LVEd_MultiEditSubclass()
    LOCAL p AS LVEdT PTR
    LOCAL szTab AS ASCIIZ * 2

    DIALOG GET USER CB.HNDL, %LVEdUserIndex TO p

    SELECT CASE AS LONG CB.MSG
    CASE %WM_GETDLGCODE
        FUNCTION = %DLGC_WANTALLKEYS
        EXIT FUNCTION
    CASE %WM_CHAR
        SELECT CASE CB.WPARAM
        CASE %VK_TAB
            szTab = $TAB
            SendMessage(CB.HNDL, %EM_REPLACESEL, 1, VARPTR(szTab))
            SetFocus(CB.HNDL)
            EXIT FUNCTION
        END SELECT
    CASE %WM_KEYDOWN
        SELECT CASE CB.WPARAM
        CASE %VK_S
            IF (GetKeyState(%VK_CONTROL) AND &HF0000000) THEN
                CONTROL SEND @p.MEdit_hDlg, @p.MEdit_BtnOkID, %BM_CLICK, 0, 0
                EXIT FUNCTION
            END IF
        CASE %VK_A
            IF (GetKeyState(%VK_CONTROL) AND &HF0000000) THEN
                CONTROL SEND @p.MEdit_hDlg, @p.MEdit_TxtID, %EM_SETSEL, 0, -1
                EXIT FUNCTION
            END IF
        CASE %LVEdMultiEditHotKey
            CONTROL SEND @p.MEdit_hDlg, @p.MEdit_BtnOkID, %BM_CLICK, 0, 0
            EXIT FUNCTION
        CASE %VK_ESCAPE
            CONTROL SEND @p.MEdit_hDlg, @p.MEdit_BtnCancelID, %BM_CLICK, 0, 0
            EXIT FUNCTION
         END SELECT
    END SELECT

    FUNCTION = CallWindowProc(@p.MEdit_TxtOldProc, CB.HNDL, CB.MSG, CB.WPARAM, CB.LPARAM)
END FUNCTION
