@@ -19,6 +19,17 @@ static WNDPROC g_pfnEditOverlayProc = NULL;
1919static int g_editRow = -1 ; /* Display row being edited */
2020static int g_editCol = -1 ; /* Display column being edited */
2121
22+ /* Undo state for deleted rows */
23+ #define UNDO_MAX_BYTES 65536 /* 64KB limit for undo cache */
24+ typedef struct {
25+ char * * values ; /* Column values (NULL-terminated strings) */
26+ int numCols ;
27+ } UndoRow ;
28+ static UndoRow * g_undoStack = NULL ;
29+ static int g_undoCount = 0 ;
30+ static int g_undoCapacity = 0 ;
31+ static int g_undoBytes = 0 ; /* Current memory usage */
32+
2233/* Forward declarations */
2334static void StartCellEdit (int row , int col );
2435static void CommitCellEdit (void );
@@ -28,6 +39,8 @@ static void InitInsertMode(void);
2839static void ClearInsertMode (void );
2940static void CommitInsert (void );
3041static int NextEditableColumn (int col , int direction );
42+ static void PushUndo (char * * values , int numCols );
43+ static void UndoDelete (void );
3144
3245void CreateGridView (HWND hwndParent , int x , int y , int cx , int cy ) {
3346 HIMAGELIST hIml ;
@@ -122,6 +135,11 @@ LRESULT CALLBACK GridProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
122135 CopySelectedRow ();
123136 return 0 ;
124137 }
138+ /* Ctrl+Z - Undo last delete (only in edit mode) */
139+ if (ctrl && wParam == 'Z' && g_editMode ) {
140+ if (g_undoCount > 0 ) UndoDelete ();
141+ return 0 ;
142+ }
125143 /* Ctrl+F - Find */
126144 if (ctrl && wParam == 'F' ) {
127145 DoFind ();
@@ -790,6 +808,180 @@ static void CancelCellEdit(void) {
790808 SetFocus (g_hwndGrid );
791809}
792810
811+ /*============================================================================
812+ ** Undo Support for Row Deletion
813+ **============================================================================*/
814+
815+ static void FreeUndoRow (UndoRow * row ) {
816+ int i ;
817+ if (row -> values ) {
818+ for (i = 0 ; i < row -> numCols ; i ++ ) {
819+ if (row -> values [i ]) LocalFree (row -> values [i ]);
820+ }
821+ LocalFree (row -> values );
822+ }
823+ }
824+
825+ void ClearUndoStack (void ) {
826+ int i ;
827+ for (i = 0 ; i < g_undoCount ; i ++ ) {
828+ FreeUndoRow (& g_undoStack [i ]);
829+ }
830+ if (g_undoStack ) LocalFree (g_undoStack );
831+ g_undoStack = NULL ;
832+ g_undoCount = 0 ;
833+ g_undoCapacity = 0 ;
834+ g_undoBytes = 0 ;
835+ }
836+
837+ /* Check available memory before caching */
838+ static int CanCacheUndo (int bytesNeeded ) {
839+ /* Drop oldest entries if over limit */
840+ while (g_undoCount > 0 && g_undoBytes + bytesNeeded > UNDO_MAX_BYTES ) {
841+ int oldBytes = g_undoStack [0 ].numCols * 32 ;
842+ int j ;
843+ FreeUndoRow (& g_undoStack [0 ]);
844+ g_undoBytes -= oldBytes ;
845+ if (g_undoBytes < 0 ) g_undoBytes = 0 ;
846+ g_undoCount -- ;
847+ for (j = 0 ; j < g_undoCount ; j ++ )
848+ g_undoStack [j ] = g_undoStack [j + 1 ];
849+ }
850+ return 1 ;
851+ }
852+
853+ static void PushUndo (char * * values , int numCols ) {
854+ int i , rowBytes = 0 ;
855+ UndoRow * row ;
856+
857+ if (!values || numCols < 1 ) return ;
858+
859+ /* Estimate memory needed */
860+ for (i = 0 ; i < numCols ; i ++ ) {
861+ if (values [i ]) {
862+ int len = 0 ;
863+ while (values [i ][len ]) len ++ ;
864+ rowBytes += len + 1 ;
865+ }
866+ }
867+ rowBytes += numCols * sizeof (char * );
868+
869+ if (!CanCacheUndo (rowBytes )) return ;
870+
871+ /* Grow stack if needed */
872+ if (g_undoCount >= g_undoCapacity ) {
873+ int newCap = g_undoCapacity ? g_undoCapacity * 2 : 8 ;
874+ UndoRow * newStack = (UndoRow * )LocalAlloc (LMEM_FIXED , newCap * sizeof (UndoRow ));
875+ if (!newStack ) return ;
876+ if (g_undoStack ) {
877+ for (i = 0 ; i < g_undoCount ; i ++ ) newStack [i ] = g_undoStack [i ];
878+ LocalFree (g_undoStack );
879+ }
880+ g_undoStack = newStack ;
881+ g_undoCapacity = newCap ;
882+ }
883+
884+ /* Copy row data */
885+ row = & g_undoStack [g_undoCount ];
886+ row -> numCols = numCols ;
887+ row -> values = (char * * )LocalAlloc (LMEM_FIXED , numCols * sizeof (char * ));
888+ if (!row -> values ) return ;
889+
890+ for (i = 0 ; i < numCols ; i ++ ) {
891+ if (values [i ]) {
892+ int len = 0 , j ;
893+ while (values [i ][len ]) len ++ ;
894+ row -> values [i ] = (char * )LocalAlloc (LMEM_FIXED , len + 1 );
895+ if (row -> values [i ]) {
896+ for (j = 0 ; j <= len ; j ++ ) row -> values [i ][j ] = values [i ][j ];
897+ }
898+ } else {
899+ row -> values [i ] = NULL ;
900+ }
901+ }
902+
903+ g_undoCount ++ ;
904+ g_undoBytes += rowBytes ;
905+ }
906+
907+ static void UndoDelete (void ) {
908+ UndoRow * row ;
909+ char sql [2048 ];
910+ char * p ;
911+ const char * s ;
912+ int i , rc ;
913+ char * errmsg = NULL ;
914+ char tableName [128 ];
915+
916+ if (g_undoCount < 1 || !g_editMode ) return ;
917+
918+ /* Save table name */
919+ s = g_editTableName ;
920+ p = tableName ;
921+ while (* s ) * p ++ = * s ++ ;
922+ * p = '\0' ;
923+
924+ row = & g_undoStack [g_undoCount - 1 ];
925+
926+ /* Build INSERT - skip column 0 (rowid) */
927+ p = sql ;
928+ s = "INSERT INTO \"" ;
929+ while (* s ) * p ++ = * s ++ ;
930+ s = tableName ;
931+ while (* s ) * p ++ = * s ++ ;
932+ s = "\" (" ;
933+ while (* s ) * p ++ = * s ++ ;
934+
935+ /* Column names from metadata (skip rowid) */
936+ for (i = 1 ; i < row -> numCols && i < g_colMetaCount + 1 ; i ++ ) {
937+ if (i > 1 ) * p ++ = ',' ;
938+ * p ++ = '"' ;
939+ s = g_colMeta [i - 1 ].name ;
940+ while (* s ) * p ++ = * s ++ ;
941+ * p ++ = '"' ;
942+ }
943+
944+ s = ") VALUES (" ;
945+ while (* s ) * p ++ = * s ++ ;
946+
947+ /* Values (skip rowid at index 0) */
948+ for (i = 1 ; i < row -> numCols ; i ++ ) {
949+ if (i > 1 ) * p ++ = ',' ;
950+ if (row -> values [i ]) {
951+ * p ++ = '\'' ;
952+ s = row -> values [i ];
953+ while (* s ) {
954+ if (* s == '\'' ) * p ++ = '\'' ; /* Escape quotes */
955+ * p ++ = * s ++ ;
956+ }
957+ * p ++ = '\'' ;
958+ } else {
959+ s = "NULL" ;
960+ while (* s ) * p ++ = * s ++ ;
961+ }
962+ }
963+ * p ++ = ')' ;
964+ * p ++ = ';' ;
965+ * p = '\0' ;
966+
967+ rc = sqlite_exec (g_db , sql , NULL , NULL , & errmsg );
968+ if (rc != SQLITE_OK ) {
969+ wchar_t wmsg [256 ];
970+ MultiByteToWideChar (CP_ACP , 0 , errmsg ? errmsg : "Unknown error" , -1 , wmsg , 256 );
971+ MessageBoxW (g_hwndMain , wmsg , L"Undo Failed" , MB_OK | MB_ICONERROR );
972+ if (errmsg ) sqlite_freemem (errmsg );
973+ return ;
974+ }
975+
976+ /* Remove from undo stack */
977+ FreeUndoRow (row );
978+ g_undoCount -- ;
979+
980+ /* Refresh grid */
981+ OpenTableForEditing (tableName );
982+ SendMessageW (g_hwndStatus , SB_SETTEXTW , 1 , (LPARAM )L"Row restored" );
983+ }
984+
793985static void DeleteSelectedRow (void ) {
794986 int sel , count , dataRow , rowidIdx , deleted , i ;
795987 char * rowid ;
@@ -801,6 +993,7 @@ static void DeleteSelectedRow(void) {
801993 int rc ;
802994 wchar_t msg [64 ];
803995 char * * rowids ;
996+ int * selRows ;
804997
805998 /* Count selected rows (excluding placeholder) */
806999 count = 0 ;
@@ -810,6 +1003,16 @@ static void DeleteSelectedRow(void) {
8101003 }
8111004 if (count < 1 ) return ;
8121005
1006+ /* Collect selection indices BEFORE dialog (dialog may clear selection) */
1007+ selRows = (int * )LocalAlloc (LMEM_FIXED , count * sizeof (int ));
1008+ if (!selRows ) return ;
1009+ i = 0 ;
1010+ sel = -1 ;
1011+ while ((sel = ListView_GetNextItem (g_hwndGrid , sel , LVNI_SELECTED )) >= 0 ) {
1012+ if (sel < g_lastResultRows && i < count ) selRows [i ++ ] = sel ;
1013+ }
1014+ count = i ;
1015+
8131016 /* Save table name before it gets cleared */
8141017 s = g_editTableName ;
8151018 p = tableName ;
@@ -822,36 +1025,41 @@ static void DeleteSelectedRow(void) {
8221025 else
8231026 wsprintfW (msg , L"Delete %d rows?" , count );
8241027 if (MessageBoxW (g_hwndMain , msg , L"Confirm Delete" ,
825- MB_YESNO | MB_ICONQUESTION ) != IDYES )
1028+ MB_YESNO | MB_ICONQUESTION ) != IDYES ) {
1029+ LocalFree (selRows );
8261030 return ;
1031+ }
8271032
828- /* Collect rowids first (data will be freed on re-query) */
1033+ /* Collect rowids and save row data for undo */
8291034 rowids = (char * * )LocalAlloc (LMEM_FIXED , count * sizeof (char * ));
830- if (!rowids ) return ;
1035+ if (!rowids ) { LocalFree ( selRows ); return ; }
8311036
832- i = 0 ;
833- sel = -1 ;
834- while ((sel = ListView_GetNextItem (g_hwndGrid , sel , LVNI_SELECTED )) >= 0 ) {
1037+ for (i = 0 ; i < count ; i ++ ) {
8351038 int j , len ;
836- if ( sel >= g_lastResultRows ) continue ;
1039+ sel = selRows [ i ] ;
8371040 dataRow = g_sortIndex ? g_sortIndex [sel ] : sel ;
8381041 rowidIdx = (dataRow + 1 ) * g_lastResultCols ;
8391042 rowid = g_lastResult [rowidIdx ];
840- if (rowid && i < count ) {
1043+ if (rowid ) {
1044+ /* Save row data for undo (entire row including rowid) */
1045+ PushUndo (& g_lastResult [rowidIdx ], g_lastResultCols );
1046+
8411047 len = 0 ;
8421048 while (rowid [len ]) len ++ ;
8431049 rowids [i ] = (char * )LocalAlloc (LMEM_FIXED , len + 1 );
8441050 if (rowids [i ]) {
8451051 for (j = 0 ; j <= len ; j ++ ) rowids [i ][j ] = rowid [j ];
846- i ++ ;
8471052 }
1053+ } else {
1054+ rowids [i ] = NULL ;
8481055 }
8491056 }
850- count = i ; /* Actual count collected */
1057+ LocalFree ( selRows );
8511058
8521059 /* Delete by rowid */
8531060 deleted = 0 ;
8541061 for (i = 0 ; i < count ; i ++ ) {
1062+ if (!rowids [i ]) continue ;
8551063 p = sql ;
8561064 s = "DELETE FROM \"" ;
8571065 while (* s ) * p ++ = * s ++ ;
@@ -891,10 +1099,6 @@ static void DeleteSelectedRow(void) {
8911099 }
8921100}
8931101
894- /*============================================================================
895- ** Insert Mode - editing the placeholder row
896- **============================================================================*/
897-
8981102static void ClearInsertMode (void ) {
8991103 int i ;
9001104 if (g_pendingValues ) {
0 commit comments