Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
QB64PE Excel-type spreadsheet supporting formulas and QB64PE macros?
#21
If anything QB64 users can learn a lot from both your examples! 
I'm not sure how far I'll get with it, but it's a great start. Thank you both!
Reply
#22
Quote:@madscijr - Anyway, it's not broke (yet) but with all the annoying and constant changes Microsoft is determined to force on us (the latest annoyance being the macro recorder now records "Office scripts" by default instead of the standard VBA macros, which although you can disable it, is just another unwanted change and a sign that MS is moving to phase BASIC out of their products (vbscript is already on the chopping block)). Also the cost of Office means you can't share this stuff with someone if they don't have a subscription. 
Take a look here; the relationship between VBA for Excel and Office Script is explained very well (have it translated):

Office-Script vs. VBA: Excel – Programmierung für Web und Desktop

Office Script will not replace VBA; it is merely a complement for specific use cases.
Reply
#23
Well that's good, because I have tons and tons of VBA macros that I use daily with Excel, Word, etc.! (If only they supported macros for OneNote! Microsoft what are you thinking? LoL)
Thanks for sharing that - will check it out later. 
PS Isn't it nice we have google to translate this? :-)
Reply
#24
Implemented some editing - kind of buggy, if you press Esc to cancel edit mode, it sets a flag so that the Esc doesn't also quit out of the program until the keyup event is detected, but something's not right, because then Esc stops being detected after that. 
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)

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

' -----------------------------------------------------------------------------
' UNDER CONSTRUCTION: Phase 1-A
' -----------------------------------------------------------------------------
' 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)
'
' *CURRENT BUG*
' Currently when you edit then exit when cursor is visible
' the cursor character & sometimes extra characters are saved to the cell.
' TODO: make sure cursor is erased when we exit

' -----------------------------------------------------------------------------
' Phase 1-B
' -----------------------------------------------------------------------------
' 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

' Selecting one or more columns or rows with mouse:
' - Clicking a column or row header selects the whole column
' - Click a column or row header and drag to select multiple columns or rows
' - Click a column or row header then hold down Shift + click a 2nd column or row header to select all in between

' DESIGN GOAL:
' Design so that all these features the user can do in the UI,
' can be automated & programmed in the codebehind as easily as possible.

' -----------------------------------------------------------------------------
' 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

' Add ability to resize column widths
' - by clicking on edge of column and dragging
' - by pressing a hotkey for "column width" inputbox

' -----------------------------------------------------------------------------
' 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
   
    ' Phase 1 Additions to track the editing state and the cursor position:
    IsEditing As _Byte
    EditBuffer As String
    CursorPos As Integer
    TempFirstCol As Long
    IsCancellingEdit As _Byte
   
End Type ' GridView

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' 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
Dim Shared IsRunning As Integer

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

' Initialize
'$Dynamic
Screen _NewImage(1024, 768, 32)
_Title "QB_Sheets Core Engine"
InitKeyCodes
MainGrid.Handle = Sheet_New(10000, 100)
MainGrid.IsEditing = _FALSE
MainGrid.EditBuffer = ""
MainGrid.CursorPos = 0
MainGrid.TempFirstCol = 0
MainGrid.IsCancellingEdit = _FALSE

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

IsRunning = _TRUE

' ================================================================================================================================================================
' MAIN LOOP
' ================================================================================================================================================================
Do
    _Limit 60
    HandleGridInput MainGrid
   
    Cls '_RGB32(50, 50, 50)
    DrawGridWithHeaders MainGrid, 0, 0, _Width, _Height
   
   
    If arrKeyState(cKeyEsc) = _FALSE Then
    End If
   
    _Display
Loop Until IsRunning = _FALSE

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
           
            ' BEGIN Remember the TempFirstCol when a user selects a cell.
            ' Save current buffer if switching cells while editing
            If G.IsEditing Then CommitEdit G
           
            G.SelR1 = r: G.SelC1 = c
            G.SelR2 = r: G.SelC2 = c
            G.TempFirstCol = c ' Remember the column for Enter key logic [cite: 183]
            G.IsDragging = -1
           
            ' TODO: Double-click to enter edit mode
            ' (Basic implementation: check if same cell clicked rapidly)
            ' Add double-click timer logic here if needed
           
        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
    Dim dispText$
   
    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 inside cell
            If G.IsEditing And r = G.SelR1 And c = G.SelC1 Then
                ' Provide visual feedback if editing
               
                ' Highlight editing cell
                Line (curX, curY)-(curX + colW - 1, curY + rowH - 1), _RGB32(255, 255, 255), BF
                Color _RGB32(0, 0, 0)
               
                ' Show a blinking cursor
                ' TODO: make sure cursor is erased when we exit
                ' TODO: currently when you edit then exit when cursor is visible
                ' TODO: the cursor character & sometimes extra characters are saved to the cell
                dispText$ = G.EditBuffer
                If (Timer * 2) Mod 2 = 0 Then dispText$ = dispText$ + "|"
                _PrintString (curX + 4, curY + (rowH / 2 - 8)), dispText$
            Else
                ' Draw Text Content
                If Prop.VarType > 0 Then
                    _PrintString (curX + 4, curY + (rowH / 2 - 8)), Sheet_GetSTR$(G.Handle, r, c)
                End If
            End If
           
            ' Goto next column
            curX = curX + colW
            c = c + 1
        Loop
       
        ' Goto next row
        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

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN Implement Editing Subs
'   New subroutines to manage the edit lifecycle
'   (F2 to enter, typing, Enter/Tab to save, Esc to cancel).
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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

Sub TriggerEditMode (G As GridView, bClearCell As Integer)
    G.IsEditing = -1
    If bClearCell = _FALSE Then
        G.EditBuffer = Sheet_GetSTR$(G.Handle, G.SelR1, G.SelC1)
    Else
        G.EditBuffer = ""
    End If
    G.CursorPos = Len(G.EditBuffer) + 1
End Sub ' TriggerEditMode

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

Sub CommitEdit (G As GridView)
    Sheet_SetSTR G.Handle, G.SelR1, G.SelC1, G.EditBuffer
    G.IsEditing = 0
    G.EditBuffer = ""
End Sub ' CommitEdit

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

Sub CancelEdit (G As GridView)
    G.IsEditing = 0
    G.EditBuffer = ""
    G.IsCancellingEdit = _TRUE
End Sub ' CancelEdit

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END Implement Editing Subs
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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

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%
    Dim k$
   
    ' UPDATE KEYBOARD INPUT
    While _DeviceInput(1): Wend ' clear and update the keyboard buffer
    kh% = _KeyHit
   
    ' -----------------------------------------------------------------------------
    ' Process text input
    ' -----------------------------------------------------------------------------
    ' Handle Edit Mode Input
    ' TODO: support cursor + Home + End keys to move left/right/etc. while editing
    If G.IsEditing Then
        Select Case kh%
            Case arrKeyHitDown(cKeyEsc) ' Esc: Revert and exit
                ' TODO: fix Esc key being disabled when we Esc out of edit mode so user can use Esc to quit
                'arrKeyState(cKeyEsc) = _TRUE
                CancelEdit G
            Case arrKeyHitDown(cKeyEnter) ' Enter: Save and move down [cite: 183]
                arrKeyState(cKeyEnter) = _TRUE
                CommitEdit G
                'TODO: make sure cursor is erased when we exit & no unwanted text saved to cell
                G.SelR1 = G.SelR1 + 1: G.SelC1 = G.TempFirstCol
                G.SelR2 = G.SelR1: G.SelC2 = G.SelC1
            Case arrKeyHitDown(cKeyTab) ' Tab: Save and move right
                arrKeyState(cKeyTab) = _TRUE
                CommitEdit G
                'TODO: make sure cursor is erased when we exit & no unwanted text saved to cell
                G.SelC1 = G.SelC1 + 1: G.SelC2 = G.SelC1
            Case arrKeyHitDown(cKeyBackspace) ' Backspace
                arrKeyState(cKeyBackspace) = _TRUE
                If Len(G.EditBuffer) > 0 Then
                    G.EditBuffer = Left$(G.EditBuffer, Len(G.EditBuffer) - 1)
                End If
            Case Else
                k$ = InKey$
                ' TODO: IGNORE KEY CODES WE DON'T WANT
                If Len(k$) = 1 Then
                    If Asc(k$) >= 32 Then
                        G.EditBuffer = G.EditBuffer + k$ ' Type values/formulas
                    End If
                End If
        End Select
       
        ' Skip navigation keys while editing
        GoTo ClearKeyboardBuffer
    End If
   
    ' -----------------------------------------------------------------------------
    ' DETECT KEYS WITH _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
   
    ' TAB MOVES ONE CELL OVER
    If kh% = arrKeyHitDown(cKeyTab) Then
        If arrKeyState(cKeyTab) = _FALSE Then
            arrKeyState(cKeyTab) = _TRUE
            G.SelC1 = G.SelC1 + 1: G.SelC2 = G.SelC1
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyTab) Then
        arrKeyState(cKeyTab) = _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, _FALSE
            '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

    ' BACKSPACE CLEARS CELL AND ENTERS EDIT MODE
    If kh% = arrKeyHitDown(cKeyBackspace) Then
        If arrKeyState(cKeyBackspace) = _FALSE Then
            arrKeyState(cKeyBackspace) = _TRUE
            TriggerEditMode G, _TRUE
            'Sheet_SetSTR MainGrid.Handle, G.SelR1, G.SelC1, "F2=edit"
            GoTo ClearKeyboardBuffer
        End If
    ElseIf kh% = arrKeyHitUp(cKeyBackspace) Then
        arrKeyState(cKeyBackspace) = _FALSE
        GoTo ClearKeyboardBuffer
    End If
   
    ' ESC = QUIT PROGRAM
    If kh% = arrKeyHitDown(cKeyEsc) Then
        If G.IsCancellingEdit = _FALSE Then
            If arrKeyState(cKeyEsc) = _FALSE Then
                arrKeyState(cKeyEsc) = _TRUE
                IsRunning = _FALSE
                GoTo ClearKeyboardBuffer
            End If
        End If
    ElseIf kh% = arrKeyHitUp(cKeyEsc) Then
        G.IsCancellingEdit = _FALSE
        arrKeyState(cKeyEsc) = _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, _FALSE
                '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, _FALSE
    '   Case Chr$(0) + Chr$(60): ' F2 key = start editing
    '       'TriggerEditMode G, _ FALSE
    '       '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
   
    ' TODO: (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
#25
@madscijr 

First i'd say use inform to do you GUI aspects. Then we can look at implementing Tabs for multiple sheets (the c++ back end already supports this). Date and time fields can be stored in the string data but a few new flags to denote the extended data type's would be useful (varType in the Cell data udt(need to add em to both the c++ and the Qb64 side)). Current max for cols/rows is 1000000 i think but you can change that value in the HandleGrid sub to use new ones stored in the Grid udt. The only limit to the number of cells is based on your ram so the sheets can be as big as you want/need/can accommodate.

Good luck and happy coding.

Unseen
Reply
#26
Thanks, I've been meaning to play with InForm for a while, this could very well be a good project to finally get my feet wet...
Reply
#27
@madscijr

OMG dont even try such a complex thing to get your feet wet with InForm!

Start by learning to use Textboxes after Buttons, all the control tools first! because the ListBox is what you will need for this spreadsheet stuff and that takes some learning just in itself as arrays dont fit in UDT's for Controls so you have to figure out how all that works. Dont let your eyes get bigger that your brain digestion. Smile

The Spreadsheet thing is a great goal, a "Why learn InForm anyway?" Answer.

Learning anything takes focus and a good strong Why! for that focus, unless you are a freak'n genius that can pickup things on first exposure and honestly not seeing that in your coding style, yet!
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply
#28
Sounds reasonable... My eyes are often bigger than my stomach where projects are concerned... The more I think about it, the more it seems I bit off more than I can chew... Feel free to play with the spreadsheet, it will probably take a team of people to whip into shape! For now, I'm just happy to have this grid control, and @Petr's non-GUI version with its working calculations can come in handy too... Next stop for me is to try playing with InForm... Is there a guide for dummies for that? I mean real basic, like hello world basic... "download x file from {url}, put it in y folder with your code, then make a new .bas with z code in it"... because I find it kinda intimidating. One big reason I like QB64 is it's all just one EXE and works right out of the box, no setup necessary. It's stuff with tricky or tedious little details that trips me up when getting into new things, for example Free Basic and Free Pascal seemed a little complicated to set up and Linux led down so many rabbit holes and I burned a lot of time there... Anyway enough yapping, I'll go check out InForm soon!
Reply
#29
Each control is described in detail in the help just at the place you download the InForm code like QB64 command words in Wiki but it is up to you to put the controls together to work with each other and QB64 code.
    That is another aspect to InForm and GUI apps.
The Form designer that helps you visualize the layout of your controls, that will be your introduction when you start practice with Buttons and TextBoxes. I don't know if there is a tut for that but it's pretty intuitive when you are inside the designer.
  724  855  599  923  575  468  400  206  147  564  878  823  652  556 bxor cross forever
Reply
#30
Ok cool!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  QB64pe and Home Automation dano 3 199 02-12-2026, 02:27 PM
Last Post: mdijkens
Question qb64pe's translation of .bas to .cpp ? Fifi 6 469 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 139 01-20-2026, 02:53 PM
Last Post: Fifi
  a question about OpenGL in QB64pe: TempodiBasic 11 1,735 11-22-2025, 05:47 PM
Last Post: TempodiBasic
Question Latest version of QB64PE or QB64 compatible with Windows XP (32-bit)? madscijr 14 1,932 09-30-2025, 08:10 AM
Last Post: hsiangch_ong

Forum Jump:


Users browsing this thread: