Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
QB64PE Excel-type spreadsheet supporting formulas and QB64PE macros?
#11
Code: (Select All)
#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
Again save that as QB_Sheets.h

Code: (Select All)
'-------------------------------------------------------------------------
' QB_Sheets.bi - Header for Grid Logic and C++ Interface
'-------------------------------------------------------------------------

' --- TYPES ---
TYPE MouseState
  X AS LONG: Y AS LONG: B1 AS _BYTE: Wheel AS LONG
END TYPE

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

TYPE CellProperties
  Value AS STRING: IntValue AS _INTEGER64: DblValue AS DOUBLE
  BG AS _UNSIGNED LONG: FG AS _UNSIGNED LONG: Locked AS _BYTE: VarType AS _BYTE
END TYPE

TYPE GridView
  Handle AS _OFFSET
  TopRow AS LONG: LeftCol AS LONG
  SelR1 AS LONG: SelC1 AS LONG: SelR2 AS LONG: SelC2 AS LONG
  IsDragging AS _BYTE

END TYPE

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



' QB64 Spreadsheet Core
'$DYNAMIC
SCREEN _NEWIMAGE(1024, 768, 32)
_TITLE "QB_Sheets Core Engine"



' --- GLOBALS ---
DIM SHARED Mouse AS MouseState, OldMouse AS MouseState
DIM SHARED MainGrid AS GridView

' Initialize
MainGrid.Handle = Sheet_New(10000, 100)
' Populate some test data
FOR i = 0 TO 50: Sheet_SetSTR MainGrid.Handle, i, 0, "Item" + STR$(i): NEXT

' --- MAIN LOOP ---
DO
  _LIMIT 60
  UpdateInput
  HandleGridInput MainGrid

  CLS '_RGB32(50, 50, 50)
  DrawGridWithHeaders MainGrid, 0, 0, _WIDTH, _HEIGHT

  _DISPLAY
LOOP UNTIL _KEYDOWN(27)

Sheet_Free MainGrid.Handle
SYSTEM

' --- SUBS ---

SUB UpdateInput
  OldMouse = Mouse
  DO WHILE _MOUSEINPUT
    Mouse.X = _MOUSEX: Mouse.Y = _MOUSEY: Mouse.B1 = _MOUSEBUTTON(1)
    Mouse.Wheel = Mouse.Wheel + _MOUSEWHEEL
  LOOP
END SUB

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

  ' Check if mouse is actually inside the data area (not on headers)
  IF Mouse.X >= hdrW AND Mouse.Y >= hdrH THEN

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

    ' Start Select
    IF Mouse.B1 AND NOT OldMouse.B1 THEN
      G.SelR1 = r: G.SelC1 = c
      G.SelR2 = r: G.SelC2 = c
      G.IsDragging = -1
    END IF

    ' Drag Select
    IF Mouse.B1 AND G.IsDragging THEN
      G.SelR2 = r: G.SelC2 = c
    END IF

  ELSEIF Mouse.B1 AND NOT OldMouse.B1 THEN
    ' User clicked in the header area
    ' TIP: You could add logic here to select an entire Row or Column
  END IF

  ' End Drag (Always reset regardless of where the mouse is)
  IF NOT Mouse.B1 THEN G.IsDragging = 0

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



SUB DrawGrid (G AS GridView, x1, y1, x2, y2)
  ' --- Move all DIMS to start ---
  DIM curY AS LONG, curX AS LONG
  DIM r AS LONG, c AS LONG
  DIM rowH AS INTEGER, colW AS INTEGER
  DIM Prop AS CellProperties

  curY = y1
  r = G.TopRow

  DO WHILE curY < y2 AND r < 10000
    rowH = Sheet_GetSize(G.Handle, r, 1)
    curX = x1
    c = G.LeftCol

    DO WHILE curX < x2 AND c < 100
      colW = Sheet_GetSize(G.Handle, c, 0)

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

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

      ' Selection Overlay
      IF IsInRange(r, c, G.SelR1, G.SelC1, G.SelR2, G.SelC2) THEN
        LINE (curX, curY)-(curX + colW - 1, curY + rowH - 1), _RGB32(0, 120, 215, 80), BF
      END IF
      COLOR Prop.FG, Prop.BG
      ' Draw Text Content
      IF Prop.VarType > 0 THEN

        _PRINTSTRING (curX + 4, curY + (rowH / 2 - 8)), Sheet_GetSTR$(G.Handle, r, c)
      END IF

      curX = curX + colW
      c = c + 1
    LOOP
    curY = curY + rowH
    r = r + 1
  LOOP
END SUB



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


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

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

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


SUB HandleKeyboard (G AS GridView)
  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)
  END SELECT

  ' Keep selection within bounds
  IF G.SelR1 < 0 THEN G.SelR1 = 0: G.SelR2 = 0
  IF G.SelC1 < 0 THEN G.SelC1 = 0: G.SelC2 = 0

  ' Auto-Scroll Viewport if selection goes off-screen
  IF G.SelR1 < G.TopRow THEN G.TopRow = G.SelR1
  ' (Repeat similar logic for Bottom/Right bounds)
END SUB



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

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

Looks a bit more professional now...i hope it helps! And this time i promise to leave it be!

Unseen
Reply
#12
(01-26-2026, 11:07 PM)Unseen Machine Wrote: Save that as QB_Sheets.h in your Qb64 folder 
...
I wont add anything more to it but as @Petr will grasp it in a heartbeat, maybe itll prove of some use.
...
Again save that as QB_Sheets.h
...
Looks a bit more professional now...i hope it helps! And this time i promise to leave it be!
Unseen

Wow... I'll give this a look... thanks!
Reply
#13
This is an interesting approach that reminds me of the good old "Multiplan" spreadsheet from the 80's. 
Any initiative to bring QB64pe closer to Excel, to create bridges between them, is welcome.
Well done...
Why not yes ?
Reply
#14
How much of this was "vibe coded"
The noticing will continue
Reply
#15
@SpriggsySpriggs

The vibes were just too strong to resist. I think we’re both speaking the same 'language' here.

Work on this will continue. But first I'll finish a lot of other unfinished business.


Reply
#16
Mine basically 100% But that's the world nowadays. It's not like it comes out in one hit, has to be debugged and tested, features added etc...
Reply
#17
@grok, is this true?
[Image: images?q=tbn:ANd9GcTdoR1vWVzl7WBG6BrFDVU...tgxUoPug&s]
The noticing will continue
Reply
#18
I started adding to the keyboard handler, cursor keys move around and it supports _KeyHit and _Button methods. 
Next it needs to track "edit mode" and let the user type in a value & exit when they hit Tab or Enter (or Esc to cancel editing). 
Need to figure out how to highlight the cell and show the blinking cursor while editing. 

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

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

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

' SOME MODS BY MADSCIJR (STARTED IMPLEMENTING KEYBOARD INPUT)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    _Display
Loop Until _KeyDown(27)

Sheet_Free MainGrid.Handle
System

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

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

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

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

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

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

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

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

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

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

    ' Keyboard input
    HandleKeyboard G
End Sub ' HandleGridInput

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

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

    curY = y1
    r = G.TopRow

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Tile selection would come from a double click maybe!

But im glad it helped you!

Unseen

@SpriggsySpriggs and @Petr : I dont think you sarcastic and rude remarks are very nice, we have Pete for that and i find it quite hurtful. So wither be constructive or butt out the conversation!
Reply
#20
@Unseen Machine

Sorry about my “vibes” comment — it was meant as self-deprecating humor (as in “I prototyped fast”), not a jab at you or your work. I genuinely appreciate the time and effort you put into this. Let’s keep it constructive and push it forward.


Reply


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

Forum Jump:


Users browsing this thread: 1 Guest(s)