01-28-2026, 11:03 PM
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
' ****************************************************************************************************************************************************************
