Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
QB64PE Excel-type spreadsheet supporting formulas and QB64PE macros?
#18
I started adding to the keyboard handler, cursor keys move around and it supports _KeyHit and _Button methods. 
Next it needs to track "edit mode" and let the user type in a value & exit when they hit Tab or Enter (or Esc to cancel editing). 
Need to figure out how to highlight the cell and show the blinking cursor while editing. 

Updated with a "todo" list of features needed to make this useful (when you think about it, Excel really does a lot!):

Code: (Select All)
' ################################################################################################################################################################
' QB64PE SPREADSHEET
' There is value in this being a reusable programmable datagrid type control
' you can use in your own QB64 programs.

' MOSTLY BY UNSEEN MACHINE, ORIGINAL CODE AT:
' https://qb64phoenix.com/forum/showthread.php?tid=4417&pid=39358#pid39358

' SOME MODS BY MADSCIJR (STARTED IMPLEMENTING KEYBOARD INPUT)

' -----------------------------------------------------------------------------
' TODO Phase 1
' -----------------------------------------------------------------------------
' Editing:
' When user clicks on a cell, remember the column number as temporary first column
' When user presses F2 or double-clicks a cell, enter edit mode:
' - highlight the cell
' - let user type values or a formula in the cell
' - show a blinking cursor as they type
' - if user presses Tab key, save value to cell and move celection to next cell to the right
' - if user presses Enter key, save value to cell and move selection down 1 row, starting at temporary first column
' - if user presses Esc key, exit edit mode - cancel editing and do not save value to cell (revert cell to old value)
'   but don't let Esc key inadvertantly quit the program (user must release key before they can press it again to quit)

' Moving around:
' Add horizontal and vertical scroll bars to move around sheet
' for editing sheets larger than the screen.
' Support a large number of rows/columns comparable to Excel

' Clipboard:
' Support cut, copy & paste to/from clipboard
' - support standard Ctrl-x, Ctrl-c, Ctrl-v keyboard shortcuts
' - Copy should copy the cells to clipboard as a standard grid
'   (like copying from Excel or Word or an HTML table)
'   which can be pasted into Word, OneNote or other rich text editor that supports pasting from Excel or HTML
' - or to Notepad as tab-delimited text
' - Paste should copy from the clipboard to the cells
'   (like copying from an HTML or Word table or tab-delimited text into Excel)

' Simple math operations:
' Support simple formulas with basic math operations (add, subtract, multiply, divide)
' See Petr's implementation at https://qb64phoenix.com/forum/showthread.php?tid=4417&pid=39353#pid39353

' String concatenation operations:
' - Concatenate string data, Excel-like cell references like A1 or $A$1.

' Load + Save
' Implement saving/loading a spreadsheet to common formats:
' ODS (OpenOffice and LibreOffice), XLSX, tab-delimited, CSV, XML, JSON

' -----------------------------------------------------------------------------
' TODO Phase 2
' -----------------------------------------------------------------------------
' Support absolute or relative cell references for copying cells with operations
' TYPE                 IF CELL     CONTAINS     AND IS COPIED TO     THE COPY WOULD BE
' Relative reference   B3          =A2          C4                   =B3
' Absolute reference   B3          =$A$2        C4                   =$A$2
' Partially relative   B3          =$A2         C4                   =$A3
' Partially relative   B3          =A$2         C4                   =B$2

' -----------------------------------------------------------------------------
' TODO Phase 3
' -----------------------------------------------------------------------------
' Cell formatting:
' Support ability to format a cell using Excel's cell formatting syntax for
' - # of decimal places for numbers
' - 1000 separator
' - date format (m, mm, mmm, mmmm, d, dd, ddd, dddd, y, yy, yyyy, h, hh, mm, ss, AM/PM

' Call formulas defined in the QB64PE program:
' - Formulas are QB64PE functions
' - formula name is just the name of the QB64PE function
' - formula can pass in a cell address in notation such a A2
'   or absolute notation such as $A2 or A$2 or $A$2
'   or a range of cells in notation A2:C4
' - Implement a basic =SUM formula, e.g., =SUM(A2:A10) would add the values

' -----------------------------------------------------------------------------
' TODO Phase 4 + beyond
' -----------------------------------------------------------------------------
' Multiple sheets?
' Support adding multiple sheets in a document.

' Named ranges
' Support associating a cell address or range of cells with a name
' and referencing it from within another cell or formula.
' Ability to remove or edit a name or the associated address.
' Ability to reference & work with from within a QB64PE function or sub.

' Dropdown controls
' Support defining a dropdown control in a cell (or range) that pulls its values
' from a given range of cells on a given sheet.

' Formula implementation?
' For the formulas we could go a few different ways
' * Use a big CASE statement to redirect to known / supported functions.
' * Support some kind of "eval" function that passes the parameters to the desired function,
'   which is a security risk, so only support functions that are safe security-wise
'   (basically no networking)
' * Support QB64-script type parser to allow defining functions inside the data
'   to save with the sheet. This is non-trivial but Fellippe wrote one already
'   that can be repurposed at
'   https://github.com/FellippeHeitor/QB64-interpreter

' -----------------------------------------------------------------------------
' HOW TO RUN
' -----------------------------------------------------------------------------
' REQUIRES "QB_Sheets.h" IN SAME FOLDER AS THIS FILE TO COMPILE

' ################################################################################################################################################################

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' CONSTANTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' KEY CODE INDEX CONSTANTS
Const cKeyNone = 0
Const cKeyEsc = 1
Const cKeyF1 = 2
Const cKeyF2 = 3
Const cKeyF3 = 4
Const cKeyF4 = 5
Const cKeyF5 = 6
Const cKeyF6 = 7
Const cKeyF7 = 8
Const cKeyF8 = 9
Const cKeyF9 = 10
Const cKeyF10 = 11
Const cKeyF11 = 12
Const cKeyF12 = 13
Const cKeyBackTick = 14
Const cKey1 = 15
Const cKey2 = 16
Const cKey3 = 17
Const cKey4 = 18
Const cKey5 = 19
Const cKey6 = 20
Const cKey7 = 21
Const cKey8 = 22
Const cKey9 = 23
Const cKey0 = 24
Const cKeyMinus = 25
Const cKeyEqual = 26
Const cKeyBackspace = 27
Const cKeyTilde = 28
Const cKeyExclamation = 29
Const cKeyAt = 30
Const cKeyPound = 31
Const cKeyDollar = 32
Const cKeyPercent = 33
Const cKeyCaret = 34
Const cKeyAmpersand = 35
Const cKeyAsterisk = 36
Const cKeyParenOpen = 37
Const cKeyParenClose = 38
Const cKeyUnderscore = 39
Const cKeyPlus = 40
Const cKeyInsert = 41
Const cKeyHome = 42
Const cKeyPageUp = 43
Const cKeyDelete = 44
Const cKeyEnd = 45
Const cKeyPageDown = 46
Const cKeyTab = 47
Const cKeyCapsLock = 48
Const cKeyPrintScreen = 49
Const cKeyScrollLock = 50
Const cKeyPauseBreak = 51
Const cKeyEnter = 52
Const cKeySquareOpen = 53
Const cKeySquareClose = 54
Const cKeyBackSlash = 55
Const cKeyCurlyOpen = 56
Const cKeyCurlyClose = 57
Const cKeyPipe = 58
Const cKeyComma = 59
Const cKeyPeriod = 60
Const cKeySlash = 61
Const cKeyLt = 62
Const cKeyGt = 63
Const cKeyQuestion = 64
Const cKeySemicolon = 65
Const cKeyApostrophe = 66
Const cKeyColon = 67
Const cKeyQuote = 68
Const cKeyShiftLeft = 69
Const cKeyShiftRight = 70
Const cKeyCtrlLeft = 71
Const cKeyCtrlRight = 72
Const cKeyAltLeft = 73
Const cKeyAltRight = 74
Const cKeyWinLeft = 75
Const cKeyWinRight = 76
Const cKeyMenu = 77
Const cKeySpace = 78
Const cKeyLeftArrow = 79
Const cKeyUpArrow = 80
Const cKeyDownArrow = 81
Const cKeyRightArrow = 82
Const cKeyNumLock = 83
Const cKeyA = 84
Const cKeyB = 85
Const cKeyC = 86
Const cKeyD = 87
Const cKeyE = 88
Const cKeyF = 89
Const cKeyG = 90
Const cKeyH = 91
Const cKeyI = 92
Const cKeyJ = 93
Const cKeyK = 94
Const cKeyL = 95
Const cKeyM = 96
Const cKeyN = 97
Const cKeyO = 98
Const cKeyP = 99
Const cKeyQ = 100
Const cKeyR = 101
Const cKeyS = 102
Const cKeyT = 103
Const cKeyU = 104
Const cKeyV = 105
Const cKeyW = 106
Const cKeyX = 107
Const cKeyY = 108
Const cKeyZ = 109
Const cKeyUpperA = 110
Const cKeyUpperB = 111
Const cKeyUpperC = 112
Const cKeyUpperD = 113
Const cKeyUpperE = 114
Const cKeyUpperF = 115
Const cKeyUpperG = 116
Const cKeyUpperH = 117
Const cKeyUpperI = 118
Const cKeyUpperJ = 119
Const cKeyUpperK = 120
Const cKeyUpperL = 121
Const cKeyUpperM = 122
Const cKeyUpperN = 123
Const cKeyUpperO = 124
Const cKeyUpperP = 125
Const cKeyUpperQ = 126
Const cKeyUpperR = 127
Const cKeyUpperS = 128
Const cKeyUpperT = 129
Const cKeyUpperU = 130
Const cKeyUpperV = 131
Const cKeyUpperW = 132
Const cKeyUpperX = 133
Const cKeyUpperY = 134
Const cKeyUpperZ = 135
Const cKeypadSlash = 136
Const cKeypadMultiply = 137
Const cKeypadMinus = 138
Const cKeypad7Home = 139
Const cKeypad8Up = 140
Const cKeypad9PgUp = 141
Const cKeypadPlus = 142
Const cKeypad4Left = 143
Const cKeypad5 = 144
Const cKeypad6Right = 145
Const cKeypad1End = 146
Const cKeypad2Down = 147
Const cKeypad3PgDn = 148
Const cKeypadEnter = 149
Const cKeypad0Ins = 150
Const cKeypadPeriodDel = 151

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' TYPES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Type MouseState
    X As Long: Y As Long: B1 As _Byte: Wheel As Long
End Type

' Single-Cell Messenger UDT
' This is used to "fetch" and "commit" all properties of one cell at once.

Type CellProperties
    Value As String: IntValue As _Integer64: DblValue As Double
    BG As _Unsigned Long: FG As _Unsigned Long: Locked As _Byte: VarType As _Byte
End Type

Type GridView
    Handle As _Offset
    TopRow As Long: LeftCol As Long
    SelR1 As Long: SelC1 As Long: SelR2 As Long: SelC2 As Long
    IsDragging As _Byte
End Type

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' LIBRARY
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Declare Library "QB_Sheets"
    ' Lifecycle Management
    Function Sheet_New%& Alias "QBS_New" (ByVal r As Long, ByVal c As Long)
    Sub Sheet_Free Alias "QBS_Free" (ByVal h As _Offset)

    ' Persistence (Binary Save/Load)
    Function Sheet_Save& Alias "QBS_Save" (ByVal h As _Offset, filename As String)
    Function Sheet_Load%& Alias "QBS_Load" (filename As String)

    ' Data Setters (Individual)
    Sub Sheet_SetSTR Alias "QBS_SetStr" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long, v As String)
    Sub Sheet_SetINT Alias "QBS_SetInt" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long, ByVal v As _Integer64)
    Sub Sheet_SetDBL Alias "QBS_SetDbl" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long, ByVal v As Double)

    ' Data Getters (Individual)
    Function Sheet_GetSTR$ Alias "QBS_GetStr" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long)
    Function Sheet_GetINT& Alias "QBS_GetInt" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long)
    Function Sheet_GetDBL# Alias "QBS_GetDbl" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long)

    ' Metadata Fetching
    ' Note: Pass _OFFSET(CellPropertiesVariable.BG) to fill the UDT instantly from C++
    Sub Sheet_GetInfo Alias "QBS_GetInfo" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long, ByVal infoPtr As _Offset)

    ' Formatting and Physical Layout
    Sub Sheet_Format Alias "QBS_Format" (ByVal h As _Offset, ByVal r As Long, ByVal c As Long, ByVal bg As _Unsigned Long, ByVal fg As _Unsigned Long, ByVal lock As Long)
    Sub Sheet_Size Alias "QBS_Size" (ByVal h As _Offset, ByVal idx As Long, ByVal size As Long, ByVal isRow As Long)
    Function Sheet_GetSize% Alias "QBS_GetSize" (ByVal h As _Offset, ByVal idx As Long, ByVal isRow As Long)
End Declare

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' GLOBALS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dim Shared Mouse As MouseState, OldMouse As MouseState
Dim Shared MainGrid As GridView

Dim Shared arrKeyHitDown(1 To 151) As Integer ' _KEYHIT key down event codes
Dim Shared arrKeyHitUp(1 To 151) As Integer ' _KEYHIT key up event codes
Dim Shared arrButton(1 To 151) As Integer ' _BUTTON key codes
Dim Shared arrKeyState(1 To 151) As Integer ' tracks keydown and keyup for _BUTTON
Dim Shared ClickedColumn As Long: ClickedColumn = 0 ' remember column user clicks on, when they hit Enter, move down 1 row to this column

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' QB64 Spreadsheet Core
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' Initialize
'$Dynamic
Screen _NewImage(1024, 768, 32)
_Title "QB_Sheets Core Engine"
InitKeyCodes
MainGrid.Handle = Sheet_New(10000, 100)

' Populate some test data
For i = 0 To 50: Sheet_SetSTR MainGrid.Handle, i, 0, "Item" + Str$(i): Next i

' ================================================================================================================================================================
' MAIN LOOP
' ================================================================================================================================================================
Do
    _Limit 60
    HandleGridInput MainGrid
   
    Cls '_RGB32(50, 50, 50)
    DrawGridWithHeaders MainGrid, 0, 0, _Width, _Height

    _Display
Loop Until _KeyDown(27)

Sheet_Free MainGrid.Handle
System

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' SUBS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////

Sub HandleGridInput (G As GridView)
    ' --- Header Constants (Must match your DrawGridWithHeaders) ---
    Dim hdrW As Integer: hdrW = 40 ' Width of Row labels
    Dim hdrH As Integer: hdrH = 25 ' Height of Col labels

    Dim r As Long, c As Long
   
    ' Update mouse input
    OldMouse = Mouse
    Do While _MouseInput
        Mouse.X = _MouseX: Mouse.Y = _MouseY: Mouse.B1 = _MouseButton(1)
        Mouse.Wheel = Mouse.Wheel + _MouseWheel
    Loop
   
    ' Check if mouse is actually inside the data area (not on headers)
    If Mouse.X >= hdrW And Mouse.Y >= hdrH Then

        ' Adjust mouse coordinates to Grid-Space
        ' We subtract the header offsets so GetCellAtMouse sees the "0,0" of the data
        GetCellAtMouse G, Mouse.X - hdrW, Mouse.Y - hdrH, r, c

        ' Start Select
        If Mouse.B1 And Not OldMouse.B1 Then
            G.SelR1 = r: G.SelC1 = c
            G.SelR2 = r: G.SelC2 = c
            G.IsDragging = -1
        End If

        ' Drag Select
        If Mouse.B1 And G.IsDragging Then
            G.SelR2 = r: G.SelC2 = c
        End If

    ElseIf Mouse.B1 And Not OldMouse.B1 Then
        ' User clicked in the header area
        ' TIP: You could add logic here to select an entire Row or Column
    End If

    ' End Drag (Always reset regardless of where the mouse is)
    If Not Mouse.B1 Then G.IsDragging = 0

    ' Scroll (Mouse wheel works anywhere on the window)
    If Mouse.Wheel <> OldMouse.Wheel Then
        G.TopRow = G.TopRow - (Mouse.Wheel - OldMouse.Wheel)
        If G.TopRow < 0 Then G.TopRow = 0
        ' Reset wheel delta so it doesn't accumulate
        Mouse.Wheel = 0: OldMouse.Wheel = 0
    End If

    ' Keyboard input
    HandleKeyboard G
End Sub ' HandleGridInput

' /////////////////////////////////////////////////////////////////////////////

Sub DrawGrid (G As GridView, x1, y1, x2, y2)
    ' --- Move all DIMS to start ---
    Dim curY As Long, curX As Long
    Dim r As Long, c As Long
    Dim rowH As Integer, colW As Integer
    Dim Prop As CellProperties

    curY = y1
    r = G.TopRow

    Do While curY < y2 And r < 10000
        rowH = Sheet_GetSize(G.Handle, r, 1)
        curX = x1
        c = G.LeftCol

        Do While curX < x2 And c < 100
            colW = Sheet_GetSize(G.Handle, c, 0)

            ' Fetch metadata (BG, FG, Lock, VarType)
            Sheet_GetInfo G.Handle, r, c, _Offset(Prop.BG)

            ' Draw Cell Background and Border
            Line (curX, curY)-(curX + colW - 1, curY + rowH - 1), Prop.BG, BF
            Line (curX, curY)-(curX + colW - 1, curY + rowH - 1), _RGB32(200, 200, 200), B

            ' Selection Overlay
            If IsInRange(r, c, G.SelR1, G.SelC1, G.SelR2, G.SelC2) Then
                Line (curX, curY)-(curX + colW - 1, curY + rowH - 1), _RGB32(0, 120, 215, 80), BF
            End If
            Color Prop.FG, Prop.BG
            ' Draw Text Content
            If Prop.VarType > 0 Then

                _PrintString (curX + 4, curY + (rowH / 2 - 8)), Sheet_GetSTR$(G.Handle, r, c)
            End If

            curX = curX + colW
            c = c + 1
        Loop
        curY = curY + rowH
        r = r + 1
    Loop
End Sub ' DrawGrid

' /////////////////////////////////////////////////////////////////////////////

Sub GetCellAtMouse (G As GridView, mx, my, outR As Long, outC As Long)
    ' --- Move all DIMS to start ---
    Dim curX As Long, curY As Long
    Dim w As Integer, h As Integer

    ' Find Column
    curX = 0 ' Assuming grid starts at 0, adjust if x1 > 0
    outC = G.LeftCol
    Do
        w = Sheet_GetSize(G.Handle, outC, 0)
        If mx >= curX And mx < curX + w Then Exit Do
        curX = curX + w
        outC = outC + 1
        ' Safety break to prevent infinite loops on 0-width or off-screen
        If outC > 16384 Or curX > _Width Then Exit Do
    Loop

    ' Find Row
    curY = 0
    outR = G.TopRow
    Do
        h = Sheet_GetSize(G.Handle, outR, 1)
        If my >= curY And my < curY + h Then Exit Do
        curY = curY + h
        outR = outR + 1
        If outR > 1000000 Or curY > _Height Then Exit Do
    Loop
End Sub ' GetCellAtMouse

' /////////////////////////////////////////////////////////////////////////////

Function IsInRange (r, c, r1, c1, r2, c2)
    Dim minR, maxR, minC, maxC
    If r1 < r2 Then minR = r1: maxR = r2 Else minR = r2: maxR = r1
    If c1 < c2 Then minC = c1: maxC = c2 Else minC = c2: maxC = c1
    If r >= minR And r <= maxR And c >= minC And c <= maxC Then IsInRange = -1
End Function ' IsInRange

' /////////////////////////////////////////////////////////////////////////////

Function GetColName$ (c As Long)
    Dim num As Long: num = c + 1 ' Convert 0-based index to 1-based
    Dim colName As String
    Dim remainder As Integer

    Do While num > 0
        remainder = (num - 1) Mod 26
        colName = Chr$(65 + remainder) + colName
        num = (num - remainder) \ 26
    Loop
    GetColName$ = colName
End Function ' GetColName$

' /////////////////////////////////////////////////////////////////////////////

Sub DrawGridWithHeaders (G As GridView, x1, y1, x2, y2)
    Dim curY As Long, curX As Long
    Dim r As Long, c As Long
    Dim rowH As Integer, colW As Integer
    Dim Prop As CellProperties
    Dim hdrW As Integer: hdrW = 40 ' Row labels width
    Dim hdrH As Integer: hdrH = 25 ' Column labels height
    Color _RGB32(0, 0, 0), _RGBA32(0, 0, 0, 0)

    ' 1. Draw Corner Block (Intersection of headers)
    Line (x1, y1)-(x1 + hdrW - 1, y1 + hdrH - 1), _RGB32(220, 220, 220), BF
    Line (x1, y1)-(x1 + hdrW - 1, y1 + hdrH - 1), _RGB32(100, 100, 100), B

    ' 2. Draw Column Headers (A, B, C...)
    curX = x1 + hdrW: c = G.LeftCol
    Do While curX < x2
        colW = Sheet_GetSize(G.Handle, c, 0)
        Line (curX, y1)-(curX + colW - 1, y1 + hdrH - 1), _RGB32(220, 220, 220), BF
        Line (curX, y1)-(curX + colW - 1, y1 + hdrH - 1), _RGB32(100, 100, 100), B
        _PrintString (curX + (colW \ 2 - 4), y1 + 5), GetColName$(c)
        curX = curX + colW: c = c + 1
    Loop

    ' 3. Draw Row Headers (1, 2, 3...)
    curY = y1 + hdrH: r = G.TopRow
    Do While curY < y2
        rowH = Sheet_GetSize(G.Handle, r, 1)
        Line (x1, curY)-(x1 + hdrW - 1, curY + rowH - 1), _RGB32(220, 220, 220), BF
        Line (x1, curY)-(x1 + hdrW - 1, curY + rowH - 1), _RGB32(100, 100, 100), B
        _PrintString (x1 + 5, curY + 5), LTrim$(Str$(r + 1))
        curY = curY + rowH: r = r + 1
    Loop

    ' 4. Draw Main Grid Data (Shifted by hdrW and hdrH)
    DrawGrid G, x1 + hdrW, y1 + hdrH, x2, y2
End Sub ' DrawGridWithHeaders

' /////////////////////////////////////////////////////////////////////////////

Sub HandleKeyboard (G As GridView)
    Dim kh%
   
    ' UPDATE KEYBOARD INPUT
    While _DeviceInput(1): Wend ' clear and update the keyboard buffer
   
    ' -----------------------------------------------------------------------------
    ' DETECT KEYS WITH _KEYHIT *** DOESN'T WORK ***
    ' -----------------------------------------------------------------------------
    kh% = _KeyHit
   
    ' ARROW KEYS MOVE UP
    If kh% = arrKeyHitDown(cKeyUpArrow) Then
        If arrKeyState(cKeyUpArrow) = _FALSE Then
            arrKeyState(cKeyUpArrow) = _TRUE
            G.SelR1 = G.SelR1 - 1: G.SelR2 = G.SelR1
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyUpArrow) Then
        arrKeyState(cKeyUpArrow) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' ARROW KEYS MOVE DOWN
    If kh% = arrKeyHitDown(cKeyDownArrow) Then
        If arrKeyState(cKeyDownArrow) = _FALSE Then
            arrKeyState(cKeyDownArrow) = _TRUE
            G.SelR1 = G.SelR1 + 1: G.SelR2 = G.SelR1
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyDownArrow) Then
        arrKeyState(cKeyDownArrow) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' ARROW KEYS MOVE LEFT
    If kh% = arrKeyHitDown(cKeyLeftArrow) Then
        If arrKeyState(cKeyLeftArrow) = _FALSE Then
            arrKeyState(cKeyLeftArrow) = _TRUE
            G.SelC1 = G.SelC1 - 1: G.SelC2 = G.SelC1
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyLeftArrow) Then
        arrKeyState(cKeyLeftArrow) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' ARROW KEYS MOVE RIGHT
    If kh% = arrKeyHitDown(cKeyRightArrow) Then
        If arrKeyState(cKeyRightArrow) = _FALSE Then
            arrKeyState(cKeyRightArrow) = _TRUE
            G.SelC1 = G.SelC1 + 1: G.SelC2 = G.SelC1
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyRightArrow) Then
        arrKeyState(cKeyRightArrow) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' ENTER SHOULD END DATA ENTRY MODE AND MOVE TO NEXT LINE
    If kh% = arrKeyHitDown(cKeyEnter) Then
        If arrKeyState(cKeyEnter) = _FALSE Then
            arrKeyState(cKeyEnter) = _TRUE
            'CloseEditMode(G)
            'Sheet_SetSTR MainGrid.Handle, G.SelR1, G.SelC1, "Enter"
            G.SelR1 = G.SelR1 + 1: G.SelR2 = G.SelR1
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyEnter) Then
        arrKeyState(cKeyEnter) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' F2 ENTERS EDIT MODE
    If kh% = arrKeyHitDown(cKeyF2) Then
        If arrKeyState(cKeyF2) = _FALSE Then
            arrKeyState(cKeyF2) = _TRUE
            'TriggerEditMode(G)
            Sheet_SetSTR MainGrid.Handle, G.SelR1, G.SelC1, "F2=edit"
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyF2) Then
        arrKeyState(cKeyF2) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' -----------------------------------------------------------------------------
    ' DETECT KEYS WITH _BUTTON
    ' -----------------------------------------------------------------------------
    If _TRUE = _FALSE Then
        ' ARROW KEYS MOVE
        If _Button(arrButton(cKeyUpArrow)) Then
            If arrKeyState(cKeyUpArrow) = _FALSE Then
                arrKeyState(cKeyUpArrow) = _TRUE
                G.SelR1 = G.SelR1 - 1: G.SelR2 = G.SelR1
                GoTo ClearKeyboardBuffer
            End If
        Else
            arrKeyState(cKeyUpArrow) = _FALSE
        End If
   
        ' ARROW KEYS MOVE DOWN
        If _Button(arrButton(cKeyDownArrow)) Then
            If arrKeyState(cKeyDownArrow) = _FALSE Then
                arrKeyState(cKeyDownArrow) = _TRUE
                G.SelR1 = G.SelR1 + 1: G.SelR2 = G.SelR1
                GoTo ClearKeyboardBuffer
            End If
        Else
            arrKeyState(cKeyDownArrow) = _FALSE
        End If
   
        ' ARROW KEYS MOVE LEFT
        If _Button(arrButton(cKeyLeftArrow)) Then
            If arrKeyState(cKeyLeftArrow) = _FALSE Then
                arrKeyState(cKeyLeftArrow) = _TRUE
                G.SelC1 = G.SelC1 - 1: G.SelC2 = G.SelC1
                GoTo ClearKeyboardBuffer
            End If
        Else
            arrKeyState(cKeyLeftArrow) = _FALSE
        End If
   
        ' ARROW KEYS MOVE RIGHT
        If _Button(arrButton(cKeyRightArrow)) Then
            If arrKeyState(cKeyRightArrow) = _FALSE Then
                arrKeyState(cKeyRightArrow) = _TRUE
                G.SelC1 = G.SelC1 + 1: G.SelC2 = G.SelC1
                GoTo ClearKeyboardBuffer
            End If
        Else
            arrKeyState(cKeyRightArrow) = _FALSE
        End If
   
        ' ENTER SHOULD END DATA ENTRY MODE AND MOVE TO NEXT LINE
        If _Button(arrButton(cKeyEnter)) Then
            If arrKeyState(cKeyEnter) = _FALSE Then
                arrKeyState(cKeyEnter) = _TRUE
                'CloseEditMode(G)
                'Sheet_SetSTR MainGrid.Handle, G.SelR1, G.SelC1, "Enter"
                G.SelR1 = G.SelR1 + 1: G.SelR2 = G.SelR1
                GoTo ClearKeyboardBuffer
            End If
        ElseIf _KeyHit = arrKeyHitUp(cKeyEnter) Then
            arrKeyState(cKeyEnter) = _FALSE
        End If
   
        ' F2 ENTERS EDIT MODE
        If _Button(arrButton(cKeyF2)) Then
            If arrKeyState(cKeyF2) = _FALSE Then
                arrKeyState(cKeyF2) = _TRUE
                'TriggerEditMode(G)
                Sheet_SetSTR MainGrid.Handle, G.SelR1, G.SelC1, "F2=edit"
                GoTo ClearKeyboardBuffer
            End If
        ElseIf _KeyHit = arrKeyHitUp(cKeyF2) Then
            arrKeyState(cKeyF2) = _FALSE
        End If
    End If
   
    '' -----------------------------------------------------------------------------
    '' DETECT KEYS WITH InKey$
    '' -----------------------------------------------------------------------------
    'Dim k$: k$ = InKey$
    'If k$ = "" Then Exit Sub
    'Select Case k$
    '   Case Chr$(0) + "H": ' Up Arrow
    '       G.SelR1 = G.SelR1 - 1: G.SelR2 = G.SelR1
    '   Case Chr$(0) + "P": ' Down Arrow
    '       G.SelR1 = G.SelR1 + 1: G.SelR2 = G.SelR1
    '   Case Chr$(0) + "K": ' Left Arrow
    '       G.SelC1 = G.SelC1 - 1: G.SelC2 = G.SelC1
    '   Case Chr$(0) + "M": ' Right Arrow
    '       G.SelC1 = G.SelC1 + 1: G.SelC2 = G.SelC1
    '   Case Chr$(13): ' Enter Key - Start Editing
    '       'TriggerEditMode(G)
    '   Case Chr$(0) + Chr$(60): ' F2 key = start editing
    '       'TriggerEditMode(G)
    '       'Sheet_SetSTR MainGrid.Handle, G.SelR1, G.SelC1, "F2=edit"
    'End Select
   
    ' -----------------------------------------------------------------------------
    ' RESUME AFTER KEY DETECTION
    ' -----------------------------------------------------------------------------
    ClearKeyboardBuffer:
    _KeyClear ' CLEAR KEYBOARD BUFFER
    '_Delay .25
   
    ' Keep selection within bounds
    If G.SelR1 < 0 Then G.SelR1 = 0: G.SelR2 = 0
    If G.SelC1 < 0 Then G.SelC1 = 0: G.SelC2 = 0
   
    ' Auto-Scroll Viewport if selection goes off-screen
    If G.SelR1 < G.TopRow Then G.TopRow = G.SelR1
   
    ' (Repeat similar logic for Bottom/Right bounds)
   
End Sub ' HandleKeyboard

' /////////////////////////////////////////////////////////////////////////////

Sub ExportToJSON (G As GridView, FileName As String)
    Dim Q As String: Q = Chr$(34) ' Store the quote character
    Open FileName For Output As #1
    Print #1, "{"
    Print #1, "  " + Q + "rows" + Q + ": ["
    For r = 0 To TotalRows - 1
        Print #1, "    ["
        For c = 0 To TotalCols - 1
            txt$ = Sheet_GetSTR$(G.Handle, r, c)
            ' Wraps content in quotes: "Value"
            Print #1, "      " + Q + txt$ + Q;
            If c < TotalCols - 1 Then Print #1, "," Else Print #1, ""
        Next c
        Print #1, "    ]";
        If r < TotalRows - 1 Then Print #1, "," Else Print #1, ""
    Next r
    Print #1, "  ]"
    Print #1, "}"
    Close #1
End Sub ' ExportToJSON

' /////////////////////////////////////////////////////////////////////////////

Sub ExportToXML (G As GridView, FileName As String)
    Dim Q As String: Q = Chr$(34)
    Open FileName For Output As #1
    Print #1, "<?xml version=" + Q + "1.0" + Q + " encoding=" + Q + "UTF-8" + Q + "?>"
    Print #1, "<Workbook>"
    For r = 0 To TotalRows - 1
        ' Example: <Row id="0">
        Print #1, "  <Row id=" + Q + LTrim$(Str$(r)) + Q + ">"
        For c = 0 To TotalCols - 1
            txt$ = Sheet_GetSTR$(G.Handle, r, c)
            Print #1, "    <Cell col=" + Q + LTrim$(Str$(c)) + Q + ">" + txt$ + "</Cell>"
        Next c
        Print #1, "  </Row>"
    Next r
    Print #1, "</Workbook>"
    Close #1
End Sub ' ExportToXML

' /////////////////////////////////////////////////////////////////////////////

Sub InitKeyCodes
    ' Keydown codes for _KeyHit (value of 0 means undetectable or ambiguous, use _BUTTON or other method)
    arrKeyHitDown(cKeyEsc) = 27
    arrKeyHitDown(cKeyF1) = 15104
    arrKeyHitDown(cKeyF2) = 15360
    arrKeyHitDown(cKeyF3) = 15616
    arrKeyHitDown(cKeyF4) = 15872
    arrKeyHitDown(cKeyF5) = 16128
    arrKeyHitDown(cKeyF6) = 16384
    arrKeyHitDown(cKeyF7) = 16640
    arrKeyHitDown(cKeyF8) = 16896
    arrKeyHitDown(cKeyF9) = 17152
    arrKeyHitDown(cKeyF10) = 17408
    arrKeyHitDown(cKeyF11) = -31488
    arrKeyHitDown(cKeyF12) = -31232
    arrKeyHitDown(cKeyBackTick) = 96
    arrKeyHitDown(cKey1) = 49
    arrKeyHitDown(cKey2) = 50
    arrKeyHitDown(cKey3) = 51
    arrKeyHitDown(cKey4) = 52
    arrKeyHitDown(cKey5) = 53
    arrKeyHitDown(cKey6) = 54
    arrKeyHitDown(cKey7) = 55
    arrKeyHitDown(cKey8) = 56
    arrKeyHitDown(cKey9) = 57
    arrKeyHitDown(cKey0) = 48
    arrKeyHitDown(cKeyMinus) = 45
    arrKeyHitDown(cKeyEqual) = 61
    arrKeyHitDown(cKeyBackspace) = 8
    arrKeyHitDown(cKeyTilde) = 126
    arrKeyHitDown(cKeyExclamation) = 33
    arrKeyHitDown(cKeyAt) = 64
    arrKeyHitDown(cKeyPound) = 35
    arrKeyHitDown(cKeyDollar) = 36
    arrKeyHitDown(cKeyPercent) = 37
    arrKeyHitDown(cKeyCaret) = 94
    arrKeyHitDown(cKeyAmpersand) = 38
    arrKeyHitDown(cKeyAsterisk) = 42
    arrKeyHitDown(cKeyParenOpen) = 40
    arrKeyHitDown(cKeyParenClose) = 41
    arrKeyHitDown(cKeyUnderscore) = 95
    arrKeyHitDown(cKeyPlus) = 43
    arrKeyHitDown(cKeyInsert) = 20992
    arrKeyHitDown(cKeyHome) = 18176
    arrKeyHitDown(cKeyPageUp) = 18688
    arrKeyHitDown(cKeyDelete) = 21248
    arrKeyHitDown(cKeyEnd) = 20224
    arrKeyHitDown(cKeyPageDown) = 20736
    arrKeyHitDown(cKeyTab) = 9
    arrKeyHitDown(cKeyCapsLock) = -30771
    arrKeyHitDown(cKeyPrintScreen) = 30771
    arrKeyHitDown(cKeyScrollLock) = -145
    arrKeyHitDown(cKeyPauseBreak) = 0
    arrKeyHitDown(cKeyEnter) = 13
    arrKeyHitDown(cKeySquareOpen) = 91
    arrKeyHitDown(cKeySquareClose) = 93
    arrKeyHitDown(cKeyBackSlash) = 92
    arrKeyHitDown(cKeyCurlyOpen) = 123
    arrKeyHitDown(cKeyCurlyClose) = 125
    arrKeyHitDown(cKeyPipe) = 124
    arrKeyHitDown(cKeyComma) = 44
    arrKeyHitDown(cKeyPeriod) = 46
    arrKeyHitDown(cKeySlash) = 47
    arrKeyHitDown(cKeyLt) = 60
    arrKeyHitDown(cKeyGt) = 62
    arrKeyHitDown(cKeyQuestion) = 63
    arrKeyHitDown(cKeySemicolon) = 59
    arrKeyHitDown(cKeyApostrophe) = 39
    arrKeyHitDown(cKeyColon) = 58
    arrKeyHitDown(cKeyQuote) = 34
    arrKeyHitDown(cKeyShiftLeft) = 30768
    arrKeyHitDown(cKeyShiftRight) = 30769
    arrKeyHitDown(cKeyCtrlLeft) = -30766
    arrKeyHitDown(cKeyCtrlRight) = -30767
    arrKeyHitDown(cKeyAltLeft) = -30764
    arrKeyHitDown(cKeyAltRight) = -30765
    arrKeyHitDown(cKeyWinLeft) = 0
    arrKeyHitDown(cKeyWinRight) = 0
    arrKeyHitDown(cKeyMenu) = 0
    arrKeyHitDown(cKeySpace) = 32
    arrKeyHitDown(cKeyLeftArrow) = 19200
    arrKeyHitDown(cKeyUpArrow) = 18432
    arrKeyHitDown(cKeyDownArrow) = 20480
    arrKeyHitDown(cKeyRightArrow) = 19712
    arrKeyHitDown(cKeyNumLock) = 30772
    arrKeyHitDown(cKeyA) = 97
    arrKeyHitDown(cKeyB) = 98
    arrKeyHitDown(cKeyC) = 99
    arrKeyHitDown(cKeyD) = 100
    arrKeyHitDown(cKeyE) = 101
    arrKeyHitDown(cKeyF) = 102
    arrKeyHitDown(cKeyG) = 103
    arrKeyHitDown(cKeyH) = 104
    arrKeyHitDown(cKeyI) = 105
    arrKeyHitDown(cKeyJ) = 106
    arrKeyHitDown(cKeyK) = 107
    arrKeyHitDown(cKeyL) = 108
    arrKeyHitDown(cKeyM) = 109
    arrKeyHitDown(cKeyN) = 110
    arrKeyHitDown(cKeyO) = 111
    arrKeyHitDown(cKeyP) = 112
    arrKeyHitDown(cKeyQ) = 113
    arrKeyHitDown(cKeyR) = 114
    arrKeyHitDown(cKeyS) = 115
    arrKeyHitDown(cKeyT) = 116
    arrKeyHitDown(cKeyU) = 117
    arrKeyHitDown(cKeyV) = 118
    arrKeyHitDown(cKeyW) = 119
    arrKeyHitDown(cKeyX) = 120
    arrKeyHitDown(cKeyY) = 121
    arrKeyHitDown(cKeyZ) = 122
    arrKeyHitDown(cKeyUpperA) = 65
    arrKeyHitDown(cKeyUpperB) = 66
    arrKeyHitDown(cKeyUpperC) = 67
    arrKeyHitDown(cKeyUpperD) = 68
    arrKeyHitDown(cKeyUpperE) = 69
    arrKeyHitDown(cKeyUpperF) = 70
    arrKeyHitDown(cKeyUpperG) = 71
    arrKeyHitDown(cKeyUpperH) = 72
    arrKeyHitDown(cKeyUpperI) = 73
    arrKeyHitDown(cKeyUpperJ) = 74
    arrKeyHitDown(cKeyUpperK) = 75
    arrKeyHitDown(cKeyUpperL) = 76
    arrKeyHitDown(cKeyUpperM) = 77
    arrKeyHitDown(cKeyUpperN) = 78
    arrKeyHitDown(cKeyUpperO) = 79
    arrKeyHitDown(cKeyUpperP) = 80
    arrKeyHitDown(cKeyUpperQ) = 81
    arrKeyHitDown(cKeyUpperR) = 82
    arrKeyHitDown(cKeyUpperS) = 83
    arrKeyHitDown(cKeyUpperT) = 84
    arrKeyHitDown(cKeyUpperU) = 85
    arrKeyHitDown(cKeyUpperV) = 86
    arrKeyHitDown(cKeyUpperW) = 87
    arrKeyHitDown(cKeyUpperX) = 88
    arrKeyHitDown(cKeyUpperY) = 89
    arrKeyHitDown(cKeyUpperZ) = 90
    arrKeyHitDown(cKeypadSlash) = 0
    arrKeyHitDown(cKeypadMultiply) = 0
    arrKeyHitDown(cKeypadMinus) = 0
    arrKeyHitDown(cKeypad7Home) = 0
    arrKeyHitDown(cKeypad8Up) = 0
    arrKeyHitDown(cKeypad9PgUp) = 0
    arrKeyHitDown(cKeypadPlus) = 0
    arrKeyHitDown(cKeypad4Left) = 0
    arrKeyHitDown(cKeypad5) = 53
    arrKeyHitDown(cKeypad6Right) = 0
    arrKeyHitDown(cKeypad1End) = 0
    arrKeyHitDown(cKeypad2Down) = 0
    arrKeyHitDown(cKeypad3PgDn) = 0
    arrKeyHitDown(cKeypadEnter) = 0
    arrKeyHitDown(cKeypad0Ins) = 0
    arrKeyHitDown(cKeypadPeriodDel) = 0
   
    ' Keyup codes for _KeyHit (value of 0 means undetectable or ambiguous, use _BUTTON or other method)
    arrKeyHitUp(cKeyEsc) = 27
    arrKeyHitUp(cKeyF1) = -15104
    arrKeyHitUp(cKeyF2) = -15360
    arrKeyHitUp(cKeyF3) = -15616
    arrKeyHitUp(cKeyF4) = -15872
    arrKeyHitUp(cKeyF5) = -16128
    arrKeyHitUp(cKeyF6) = -16384
    arrKeyHitUp(cKeyF7) = -16640
    arrKeyHitUp(cKeyF8) = -16896
    arrKeyHitUp(cKeyF9) = -17152
    arrKeyHitUp(cKeyF10) = -17408
    arrKeyHitUp(cKeyF11) = 31488
    arrKeyHitUp(cKeyF12) = 31232
    arrKeyHitUp(cKeyBackTick) = -96
    arrKeyHitUp(cKey1) = -49
    arrKeyHitUp(cKey2) = -50
    arrKeyHitUp(cKey3) = -51
    arrKeyHitUp(cKey4) = -52
    arrKeyHitUp(cKey5) = -53
    arrKeyHitUp(cKey6) = -54
    arrKeyHitUp(cKey7) = -55
    arrKeyHitUp(cKey8) = -56
    arrKeyHitUp(cKey9) = -57
    arrKeyHitUp(cKey0) = -48
    arrKeyHitUp(cKeyMinus) = -45
    arrKeyHitUp(cKeyEqual) = -61
    arrKeyHitUp(cKeyBackspace) = -8
    arrKeyHitUp(cKeyTilde) = -126
    arrKeyHitUp(cKeyExclamation) = -33
    arrKeyHitUp(cKeyAt) = -64
    arrKeyHitUp(cKeyPound) = -35
    arrKeyHitUp(cKeyDollar) = -36
    arrKeyHitUp(cKeyPercent) = -37
    arrKeyHitUp(cKeyCaret) = -94
    arrKeyHitUp(cKeyAmpersand) = -38
    arrKeyHitUp(cKeyAsterisk) = -42
    arrKeyHitUp(cKeyParenOpen) = -40
    arrKeyHitUp(cKeyParenClose) = -41
    arrKeyHitUp(cKeyUnderscore) = -95
    arrKeyHitUp(cKeyPlus) = -43
    arrKeyHitUp(cKeyInsert) = -20992
    arrKeyHitUp(cKeyHome) = -18176
    arrKeyHitUp(cKeyPageUp) = -18688
    arrKeyHitUp(cKeyDelete) = -21248
    arrKeyHitUp(cKeyEnd) = -20224
    arrKeyHitUp(cKeyPageDown) = -20736
    arrKeyHitUp(cKeyTab) = -9
    arrKeyHitUp(cKeyCapsLock) = -20
    arrKeyHitUp(cKeyPrintScreen) = -44
    arrKeyHitUp(cKeyScrollLock) = -145
    arrKeyHitUp(cKeyPauseBreak) = 0
    arrKeyHitUp(cKeyEnter) = -13
    arrKeyHitUp(cKeySquareOpen) = -91
    arrKeyHitUp(cKeySquareClose) = -93
    arrKeyHitUp(cKeyBackSlash) = -92
    arrKeyHitUp(cKeyCurlyOpen) = -123
    arrKeyHitUp(cKeyCurlyClose) = -125
    arrKeyHitUp(cKeyPipe) = -124
    arrKeyHitUp(cKeyComma) = -44
    arrKeyHitUp(cKeyPeriod) = -46
    arrKeyHitUp(cKeySlash) = -47
    arrKeyHitUp(cKeyLt) = -60
    arrKeyHitUp(cKeyGt) = -62
    arrKeyHitUp(cKeyQuestion) = -63
    arrKeyHitUp(cKeySemicolon) = -59
    arrKeyHitUp(cKeyApostrophe) = -39
    arrKeyHitUp(cKeyColon) = -58
    arrKeyHitUp(cKeyQuote) = -34
    arrKeyHitUp(cKeyShiftLeft) = -30768
    arrKeyHitUp(cKeyShiftRight) = -30769
    arrKeyHitUp(cKeyCtrlLeft) = 30766
    arrKeyHitUp(cKeyCtrlRight) = 30767
    arrKeyHitUp(cKeyAltLeft) = 30764
    arrKeyHitUp(cKeyAltRight) = 30765
    arrKeyHitUp(cKeyWinLeft) = 0
    arrKeyHitUp(cKeyWinRight) = 0
    arrKeyHitUp(cKeyMenu) = 0
    arrKeyHitUp(cKeySpace) = -32
    arrKeyHitUp(cKeyLeftArrow) = -19200
    arrKeyHitUp(cKeyUpArrow) = -18432
    arrKeyHitUp(cKeyDownArrow) = -20480
    arrKeyHitUp(cKeyRightArrow) = -19712
    arrKeyHitUp(cKeyNumLock) = -144
    arrKeyHitUp(cKeyA) = -97
    arrKeyHitUp(cKeyB) = -98
    arrKeyHitUp(cKeyC) = -99
    arrKeyHitUp(cKeyD) = -100
    arrKeyHitUp(cKeyE) = -101
    arrKeyHitUp(cKeyF) = -102
    arrKeyHitUp(cKeyG) = -103
    arrKeyHitUp(cKeyH) = -104
    arrKeyHitUp(cKeyI) = -105
    arrKeyHitUp(cKeyJ) = -106
    arrKeyHitUp(cKeyK) = -107
    arrKeyHitUp(cKeyL) = -108
    arrKeyHitUp(cKeyM) = -109
    arrKeyHitUp(cKeyN) = -110
    arrKeyHitUp(cKeyO) = -111
    arrKeyHitUp(cKeyP) = -112
    arrKeyHitUp(cKeyQ) = -113
    arrKeyHitUp(cKeyR) = -114
    arrKeyHitUp(cKeyS) = -115
    arrKeyHitUp(cKeyT) = -116
    arrKeyHitUp(cKeyU) = -117
    arrKeyHitUp(cKeyV) = -118
    arrKeyHitUp(cKeyW) = -119
    arrKeyHitUp(cKeyX) = -120
    arrKeyHitUp(cKeyY) = -121
    arrKeyHitUp(cKeyZ) = -122
    arrKeyHitUp(cKeyUpperA) = -65
    arrKeyHitUp(cKeyUpperB) = -66
    arrKeyHitUp(cKeyUpperC) = -67
    arrKeyHitUp(cKeyUpperD) = -68
    arrKeyHitUp(cKeyUpperE) = -69
    arrKeyHitUp(cKeyUpperF) = -70
    arrKeyHitUp(cKeyUpperG) = -71
    arrKeyHitUp(cKeyUpperH) = -72
    arrKeyHitUp(cKeyUpperI) = -73
    arrKeyHitUp(cKeyUpperJ) = -74
    arrKeyHitUp(cKeyUpperK) = -75
    arrKeyHitUp(cKeyUpperL) = -76
    arrKeyHitUp(cKeyUpperM) = -77
    arrKeyHitUp(cKeyUpperN) = -78
    arrKeyHitUp(cKeyUpperO) = -79
    arrKeyHitUp(cKeyUpperP) = -80
    arrKeyHitUp(cKeyUpperQ) = -81
    arrKeyHitUp(cKeyUpperR) = -82
    arrKeyHitUp(cKeyUpperS) = -83
    arrKeyHitUp(cKeyUpperT) = -84
    arrKeyHitUp(cKeyUpperU) = -85
    arrKeyHitUp(cKeyUpperV) = -86
    arrKeyHitUp(cKeyUpperW) = -87
    arrKeyHitUp(cKeyUpperX) = -88
    arrKeyHitUp(cKeyUpperY) = -89
    arrKeyHitUp(cKeyUpperZ) = -90
    arrKeyHitUp(cKeypadSlash) = 0
    arrKeyHitUp(cKeypadMultiply) = 0
    arrKeyHitUp(cKeypadMinus) = 0
    arrKeyHitUp(cKeypad7Home) = 0
    arrKeyHitUp(cKeypad8Up) = 0
    arrKeyHitUp(cKeypad9PgUp) = 0
    arrKeyHitUp(cKeypadPlus) = 0
    arrKeyHitUp(cKeypad4Left) = 0
    arrKeyHitUp(cKeypad5) = -53
    arrKeyHitUp(cKeypad6Right) = 0
    arrKeyHitUp(cKeypad1End) = 0
    arrKeyHitUp(cKeypad2Down) = 0
    arrKeyHitUp(cKeypad3PgDn) = 0
    arrKeyHitUp(cKeypadEnter) = 0
    arrKeyHitUp(cKeypad0Ins) = 0
    arrKeyHitUp(cKeypadPeriodDel) = 0
   
    ' Keyboard codes for _BUTTON (value of 0 means undetectable, use _KEYHIT or other method)
    arrButton(cKeyEsc) = 2
    arrButton(cKeyF1) = 60
    arrButton(cKeyF2) = 61
    arrButton(cKeyF3) = 62
    arrButton(cKeyF4) = 63
    arrButton(cKeyF5) = 64
    arrButton(cKeyF6) = 65
    arrButton(cKeyF7) = 66
    arrButton(cKeyF8) = 67
    arrButton(cKeyF9) = 68
    arrButton(cKeyF10) = 0 ' detect with _KEYHIT
    arrButton(cKeyF11) = 88
    arrButton(cKeyF12) = 89
    arrButton(cKeyBackTick) = 42
    arrButton(cKey1) = 3
    arrButton(cKey2) = 4
    arrButton(cKey3) = 5
    arrButton(cKey4) = 6
    arrButton(cKey5) = 7
    arrButton(cKey6) = 8
    arrButton(cKey7) = 9
    arrButton(cKey8) = 10
    arrButton(cKey9) = 11
    arrButton(cKey0) = 12
    arrButton(cKeyMinus) = 13
    arrButton(cKeyEqual) = 14
    arrButton(cKeyBackspace) = 15
    arrButton(cKeyTilde) = 42
    arrButton(cKeyExclamation) = 3
    arrButton(cKeyAt) = 4
    arrButton(cKeyPound) = 5
    arrButton(cKeyDollar) = 6
    arrButton(cKeyPercent) = 7
    arrButton(cKeyCaret) = 8
    arrButton(cKeyAmpersand) = 9
    arrButton(cKeyAsterisk) = 10
    arrButton(cKeyParenOpen) = 11
    arrButton(cKeyParenClose) = 12
    arrButton(cKeyUnderscore) = 13
    arrButton(cKeyPlus) = 14
    arrButton(cKeyInsert) = 339
    arrButton(cKeyHome) = 328
    arrButton(cKeyPageUp) = 330
    arrButton(cKeyDelete) = 340
    arrButton(cKeyEnd) = 336
    arrButton(cKeyPageDown) = 338
    arrButton(cKeyTab) = 16
    arrButton(cKeyCapsLock) = 59
    arrButton(cKeyPrintScreen) = 0 ' detect with _KEYHIT
    arrButton(cKeyScrollLock) = 71
    arrButton(cKeyPauseBreak) = 0 ' detect with _KEYHIT
    arrButton(cKeyEnter) = 29
    arrButton(cKeySquareOpen) = 27
    arrButton(cKeySquareClose) = 28
    arrButton(cKeyBackSlash) = 44
    arrButton(cKeyCurlyOpen) = 27
    arrButton(cKeyCurlyClose) = 28
    arrButton(cKeyPipe) = 44
    arrButton(cKeyComma) = 52
    arrButton(cKeyPeriod) = 53
    arrButton(cKeySlash) = 54
    arrButton(cKeyLt) = 52
    arrButton(cKeyGt) = 53
    arrButton(cKeyQuestion) = 54
    arrButton(cKeySemicolon) = 40
    arrButton(cKeyApostrophe) = 41
    arrButton(cKeyColon) = 40
    arrButton(cKeyQuote) = 41
    arrButton(cKeyShiftLeft) = 43
    arrButton(cKeyShiftRight) = 55
    arrButton(cKeyCtrlLeft) = 30
    arrButton(cKeyCtrlRight) = 286
    arrButton(cKeyAltLeft) = 0 ' detect with _KEYHIT
    arrButton(cKeyAltRight) = 0 ' detect with _KEYHIT
    arrButton(cKeyWinLeft) = 348
    arrButton(cKeyWinRight) = 349
    arrButton(cKeyMenu) = 350
    arrButton(cKeySpace) = 58
    arrButton(cKeyLeftArrow) = 332
    arrButton(cKeyUpArrow) = 329
    arrButton(cKeyDownArrow) = 337
    arrButton(cKeyRightArrow) = 334
    arrButton(cKeyNumLock) = 326
    arrButton(cKeyA) = 31
    arrButton(cKeyB) = 49
    arrButton(cKeyC) = 47
    arrButton(cKeyD) = 33
    arrButton(cKeyE) = 19
    arrButton(cKeyF) = 34
    arrButton(cKeyG) = 35
    arrButton(cKeyH) = 36
    arrButton(cKeyI) = 24
    arrButton(cKeyJ) = 37
    arrButton(cKeyK) = 38
    arrButton(cKeyL) = 39
    arrButton(cKeyM) = 51
    arrButton(cKeyN) = 50
    arrButton(cKeyO) = 25
    arrButton(cKeyP) = 26
    arrButton(cKeyQ) = 17
    arrButton(cKeyR) = 20
    arrButton(cKeyS) = 32
    arrButton(cKeyT) = 21
    arrButton(cKeyU) = 23
    arrButton(cKeyV) = 48
    arrButton(cKeyW) = 18
    arrButton(cKeyX) = 46
    arrButton(cKeyY) = 22
    arrButton(cKeyZ) = 45
    arrButton(cKeyUpperA) = 31
    arrButton(cKeyUpperB) = 49
    arrButton(cKeyUpperC) = 47
    arrButton(cKeyUpperD) = 33
    arrButton(cKeyUpperE) = 19
    arrButton(cKeyUpperF) = 34
    arrButton(cKeyUpperG) = 35
    arrButton(cKeyUpperH) = 36
    arrButton(cKeyUpperI) = 24
    arrButton(cKeyUpperJ) = 37
    arrButton(cKeyUpperK) = 38
    arrButton(cKeyUpperL) = 39
    arrButton(cKeyUpperM) = 51
    arrButton(cKeyUpperN) = 50
    arrButton(cKeyUpperO) = 25
    arrButton(cKeyUpperP) = 26
    arrButton(cKeyUpperQ) = 17
    arrButton(cKeyUpperR) = 20
    arrButton(cKeyUpperS) = 32
    arrButton(cKeyUpperT) = 21
    arrButton(cKeyUpperU) = 23
    arrButton(cKeyUpperV) = 48
    arrButton(cKeyUpperW) = 18
    arrButton(cKeyUpperX) = 46
    arrButton(cKeyUpperY) = 22
    arrButton(cKeyUpperZ) = 45
    arrButton(cKeypadSlash) = 310
    arrButton(cKeypadMultiply) = 56
    arrButton(cKeypadMinus) = 75
    arrButton(cKeypad7Home) = 72
    arrButton(cKeypad8Up) = 73
    arrButton(cKeypad9PgUp) = 74
    arrButton(cKeypadPlus) = 79
    arrButton(cKeypad4Left) = 76
    arrButton(cKeypad5) = 77
    arrButton(cKeypad6Right) = 78
    arrButton(cKeypad1End) = 80
    arrButton(cKeypad2Down) = 81
    arrButton(cKeypad3PgDn) = 82
    arrButton(cKeypadEnter) = 285
    arrButton(cKeypad0Ins) = 83
    arrButton(cKeypadPeriodDel) = 84
   
    ' Track key state
    Dim index As Long
    For index = LBound(arrKeyState) To UBound(arrKeyState)
        arrKeyState(index) = _FALSE
    Next index
   
End Sub ' InitKeyCodes

' ****************************************************************************************************************************************************************
' BEGIN COPY LINES BETWEEN <header> and </header> TO NEW FILE, UNCOMMENT AND SAVE AS "QB_Sheets.h" TO SAME FOLDER AS MAIN PROGRAM
' ****************************************************************************************************************************************************************
'<header>
'// Header for Grid Logic and C++ Interface
'#ifndef QB_SHEETS_H
'#define QB_SHEETS_H
'
'#include <vector>
'#include <string>
'#include <fstream>
'#include <cstdint>
'
'extern "C" {
'    typedef intptr_t QBSHandle;
'
'    // Internal cell storage structure
'    struct QBCell {
'        std::string s;
'        int64_t     i = 0;
'        double      d = 0.0;
'        uint8_t     type = 0; // 0:Empty, 1:Str, 2:Int, 3:Dbl
'        uint32_t bg = 0xFFFFFFFF; // Opaque White
'        uint32_t fg = 0xFF000000; // Opaque Black
'        bool        lock = false;
'    };
'
'    // Fast-fetch structure for QB64 (binary compatible)
'    struct QBCellInfo {
'        uint32_t bg = 0xFFFFFFFF; // Opaque White
'        uint32_t fg = 0xFF000000; // Opaque Black
'        bool     lock;
'        uint8_t  varType;
'    };
'
'    struct QBRow {
'        int16_t height = 20;
'        std::vector<QBCell> cells;
'    };
'
'    struct QBSheet {
'        std::vector<QBRow> rows;
'        std::vector<int16_t> colWidths;
'    };
'
'    // --- Lifecycle Management ---
'    __declspec(dllexport) QBSHandle QBS_New(int r, int c) {
'        QBSheet* s = new QBSheet();
'        s->rows.resize(r);
'        for (int i = 0; i < r; ++i) s->rows[i].cells.resize(c);
'        s->colWidths.assign(c, 100);
'        return (QBSHandle)s;
'    }
'
'    __declspec(dllexport) void QBS_Free(QBSHandle h) {
'        delete (QBSheet*)h;
'    }
'
'    // --- Universal Data Setters ---
'    __declspec(dllexport) void QBS_SetStr(QBSHandle h, int r, int c, const char* v) {
'        auto* s = (QBSheet*)h;
'        if (s && r < s->rows.size() && c < s->rows[r].cells.size()) {
'            s->rows[r].cells[c].s = v; s->rows[r].cells[c].type = 1;
'        }
'    }
'    __declspec(dllexport) void QBS_SetInt(QBSHandle h, int r, int c, int64_t v) {
'        auto* s = (QBSheet*)h;
'        if (s && r < s->rows.size() && c < s->rows[r].cells.size()) {
'            s->rows[r].cells[c].i = v; s->rows[r].cells[c].type = 2;
'        }
'    }
'    __declspec(dllexport) void QBS_SetDbl(QBSHandle h, int r, int c, double v) {
'        auto* s = (QBSheet*)h;
'        if (s && r < s->rows.size() && c < s->rows[r].cells.size()) {
'            s->rows[r].cells[c].d = v; s->rows[r].cells[c].type = 3;
'        }
'    }
'
'    // --- Universal Data Getters ---
'    __declspec(dllexport) const char* QBS_GetStr(QBSHandle h, int r, int c) {
'        auto* s = (QBSheet*)h;
'        if (!s || r >= s->rows.size() || c >= s->rows[r].cells.size()) return "";
'        return s->rows[r].cells[c].s.c_str();
'    }
'    __declspec(dllexport) int64_t QBS_GetInt(QBSHandle h, int r, int c) {
'        auto* s = (QBSheet*)h;
'        return (s && r < s->rows.size() && c < s->rows[r].cells.size()) ? s->rows[r].cells[c].i : 0;
'    }
'    __declspec(dllexport) double QBS_GetDbl(QBSHandle h, int r, int c) {
'        auto* s = (QBSheet*)h;
'        return (s && r < s->rows.size() && c < s->rows[r].cells.size()) ? s->rows[r].cells[c].d : 0.0;
'    }
'
'    // --- High-Speed Info Fetch ---
'    // Update this function in QB_Sheets.h
'    __declspec(dllexport) void QBS_GetInfo(QBSHandle h, int r, int c, intptr_t infoPtr) {
'        auto* s = (QBSheet*)h;
'        if (s && r < s->rows.size() && c < s->rows[r].cells.size()) {
'            // Cast the raw address back to our struct type
'            QBCellInfo* info = (QBCellInfo*)infoPtr;
'            auto& cell = s->rows[r].cells[c];
'            info->bg = cell.bg;
'            info->fg = cell.fg;
'            info->lock = cell.lock;
'            info->varType = cell.type;
'        }
'    }
'    // --- Formatting & Layout ---
'    __declspec(dllexport) void QBS_Format(QBSHandle h, int r, int c, uint32_t bg, uint32_t fg, bool lock) {
'        auto* s = (QBSheet*)h;
'        if (s && r < s->rows.size() && c < s->rows[r].cells.size()) {
'            auto& cell = s->rows[r].cells[c];
'            cell.bg = bg; cell.fg = fg; cell.lock = lock;
'        }
'    }
'
'    __declspec(dllexport) void QBS_Size(QBSHandle h, int idx, int size, bool isRow) {
'        auto* s = (QBSheet*)h; if (!s) return;
'        if (isRow && idx < s->rows.size()) s->rows[idx].height = (int16_t)size;
'        else if (!isRow && idx < s->colWidths.size()) s->colWidths[idx] = (int16_t)size;
'    }
'
'    __declspec(dllexport) int QBS_GetSize(QBSHandle h, int idx, bool isRow) {
'        auto* s = (QBSheet*)h; if (!s) return 0;
'        return isRow ? s->rows[idx].height : s->colWidths[idx];
'    }
'
'    // --- Persistence (Binary Save/Load) ---
'    __declspec(dllexport) int QBS_Save(QBSHandle h, const char* filename) {
'        auto* s = (QBSheet*)h;
'        std::ofstream ofs(filename, std::ios::binary);
'        if (!ofs) return 0;
'        uint32_t rows = s->rows.size(), cols = s->colWidths.size();
'        ofs.write((char*)&rows, 4); ofs.write((char*)&cols, 4);
'        for (auto w : s->colWidths) ofs.write((char*)&w, 2);
'        for (auto& r : s->rows) {
'            ofs.write((char*)&r.height, 2);
'            for (auto& c : r.cells) {
'                ofs.write((char*)&c.type, 1); ofs.write((char*)&c.bg, 4);
'                ofs.write((char*)&c.fg, 4); ofs.write((char*)&c.lock, 1);
'                ofs.write((char*)&c.i, 8); ofs.write((char*)&c.d, 8);
'                uint32_t slen = c.s.size(); ofs.write((char*)&slen, 4);
'                ofs.write(c.s.data(), slen);
'            }
'        }
'        return 1;
'    }
'
'    __declspec(dllexport) QBSHandle QBS_Load(const char* filename) {
'        std::ifstream ifs(filename, std::ios::binary);
'        if (!ifs) return 0;
'        uint32_t rows, cols;
'        ifs.read((char*)&rows, 4); ifs.read((char*)&cols, 4);
'        QBSheet* s = new QBSheet();
'        s->rows.resize(rows); s->colWidths.resize(cols);
'        for (int i = 0; i < cols; ++i) ifs.read((char*)&s->colWidths[i], 2);
'        for (int i = 0; i < rows; ++i) {
'            s->rows[i].cells.resize(cols);
'            ifs.read((char*)&s->rows[i].height, 2);
'            for (int j = 0; j < cols; ++j) {
'                auto& c = s->rows[i].cells[j];
'                ifs.read((char*)&c.type, 1); ifs.read((char*)&c.bg, 4);
'                ifs.read((char*)&c.fg, 4); ifs.read((char*)&c.lock, 1);
'                ifs.read((char*)&c.i, 8); ifs.read((char*)&c.d, 8);
'                uint32_t slen; ifs.read((char*)&slen, 4);
'                c.s.resize(slen); ifs.read(&c.s[0], slen);
'            }
'        }
'        return (QBSHandle)s;
'    }
'}
'#endif
'</header>
' ****************************************************************************************************************************************************************
' END COPY LINES BETWEEN <header> and </header> TO NEW FILE, UNCOMMENT AND SAVE AS "QB_Sheets.h" TO SAME FOLDER AS MAIN PROGRAM
' ****************************************************************************************************************************************************************
Reply


Messages In This Thread
RE: QB64PE Excel-type spreadsheet supporting formulas and QB64PE macros? - by madscijr - 01-28-2026, 07:05 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  QB64pe and Home Automation dano 3 219 02-12-2026, 02:27 PM
Last Post: mdijkens
Question qb64pe's translation of .bas to .cpp ? Fifi 6 488 01-21-2026, 08:51 PM
Last Post: Fifi
Question Finaly, qb64pe-4.3.0 could work on macOS 10.13.6 (High Sierra) ! Fifi 0 148 01-20-2026, 02:53 PM
Last Post: Fifi
  a question about OpenGL in QB64pe: TempodiBasic 11 1,822 11-22-2025, 05:47 PM
Last Post: TempodiBasic
Question Latest version of QB64PE or QB64 compatible with Windows XP (32-bit)? madscijr 14 2,002 09-30-2025, 08:10 AM
Last Post: hsiangch_ong

Forum Jump:


Users browsing this thread: