Skip to content

Commit 9d95a43

Browse files
committed
Schema browser enhancements: arrow key navigation, enter to examine table contents, selective DDL export, status bar refinement, advanced view showing table rowcounts and sizes (enabled from context menu); dynamic context menus per-view; enhanced results export to CSV or TXT, full-database or per-object DDL export, table export to CSV, assorted UX and workflow polish
1 parent 3551d86 commit 9d95a43

6 files changed

Lines changed: 684 additions & 14 deletions

File tree

src/sqlite-ce-edit/database.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ int OpenDatabase(const wchar_t *path) {
140140
UpdateTitle();
141141
UpdateDbSize();
142142
SetStatusResult(L"");
143+
RefreshSchema();
143144

144145
/* Show hint for in-memory database in query pane */
145146
if (path[0] == ':') {

src/sqlite-ce-edit/editor.c

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -137,14 +137,18 @@ static void UpdateContextMenu(int mode) {
137137
AppendMenuW(hCtx, MF_STRING, IDM_FINDNEXT, L"Find &Next\tF3");
138138
InsertMenuW(hCBMenu, 1, MF_BYPOSITION | MF_POPUP, (UINT)hCtx, L"&Query");
139139
} else if (mode == 1) {
140-
AppendMenuW(hCtx, MF_STRING, IDM_EXPORTCSV, L"Export as &CSV...");
141-
AppendMenuW(hCtx, MF_STRING, IDM_EXPORTTXT, L"Export as &Text...");
140+
AppendMenuW(hCtx, MF_STRING, IDM_EXPORTRESULTS, L"&Export Results...");
142141
AppendMenuW(hCtx, MF_SEPARATOR, 0, NULL);
143142
AppendMenuW(hCtx, MF_STRING, IDM_FIND, L"&Find...\tCtrl+F");
144143
AppendMenuW(hCtx, MF_STRING, IDM_FINDNEXT, L"Find &Next\tF3");
145144
InsertMenuW(hCBMenu, 1, MF_BYPOSITION | MF_POPUP, (UINT)hCtx, L"&Results");
146145
} else {
147146
AppendMenuW(hCtx, MF_STRING, IDM_REFRESH, L"&Refresh");
147+
AppendMenuW(hCtx, MF_SEPARATOR, 0, NULL);
148+
AppendMenuW(hCtx, MF_STRING, IDM_EXPORTDDL, L"Export &DDL...");
149+
AppendMenuW(hCtx, MF_STRING, IDM_EXPORTALLDDL, L"Export &All DDL...");
150+
AppendMenuW(hCtx, MF_SEPARATOR, 0, NULL);
151+
AppendMenuW(hCtx, g_showSizes ? MF_STRING | MF_CHECKED : MF_STRING, IDM_SHOWSIZES, L"Show &Details");
148152
InsertMenuW(hCBMenu, 1, MF_BYPOSITION | MF_POPUP, (UINT)hCtx, L"&Schema");
149153
}
150154

@@ -163,6 +167,11 @@ static void UpdateContextMenu(int mode) {
163167
CommandBar_DrawMenuBar(g_hwndCB, 0);
164168
}
165169

170+
void ForceMenuRebuild(void) {
171+
g_lastMenuMode = -1;
172+
UpdateContextMenu(g_viewMode);
173+
}
174+
166175
void SwitchView(int mode) {
167176
g_viewMode = mode;
168177
ShowWindow(g_hwndQuery, mode == 0 ? SW_SHOW : SW_HIDE);

src/sqlite-ce-edit/fileops.c

Lines changed: 308 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ void OpenQueryFile(const wchar_t *path) {
194194
UpdateTitle();
195195
UpdateLineNumbers();
196196
LocalFree(wbuf);
197+
/* Switch to query view */
198+
if (g_viewMode != 0) {
199+
SendMessage(g_hwndMain, WM_COMMAND, IDM_VIEWQUERY, 0);
200+
}
197201
}
198202
}
199203
if (buf) LocalFree(buf);
@@ -274,7 +278,108 @@ void DoSaveQuery(void) {
274278
}
275279

276280
/*============================================================================
277-
** Export Results to CSV
281+
** Export Results (CSV or Text based on filter selection)
282+
**============================================================================*/
283+
284+
void DoExportResults(void) {
285+
CE_OPENFILENAME ofn;
286+
wchar_t szFile[MAX_PATH] = L"results";
287+
HANDLE hFile;
288+
DWORD dwLen, dwWritten;
289+
wchar_t *wbuf, *wp;
290+
char *buf, *bp;
291+
int needQuote, isCSV;
292+
293+
memset(&ofn, 0, sizeof(ofn));
294+
ofn.lStructSize = sizeof(ofn);
295+
ofn.hwndOwner = g_hwndMain;
296+
ofn.lpstrFile = szFile;
297+
ofn.nMaxFile = MAX_PATH;
298+
ofn.lpstrFilter = L"CSV Files (*.csv)\0*.csv\0Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
299+
ofn.lpstrDefExt = NULL; /* We'll handle extension ourselves */
300+
ofn.lpstrTitle = L"Export Results";
301+
ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
302+
ofn.nFilterIndex = 1;
303+
304+
if (!GetSaveFileNameW(&ofn)) return;
305+
306+
/* Check if CSV (filter 1) or Text (filter 2+) */
307+
isCSV = (ofn.nFilterIndex == 1);
308+
309+
/* Append extension if none present */
310+
{
311+
wchar_t *p = szFile + lstrlenW(szFile);
312+
wchar_t *dot = NULL;
313+
while (p > szFile && *p != '\\') {
314+
if (*p == '.') dot = p;
315+
p--;
316+
}
317+
if (!dot) {
318+
lstrcatW(szFile, isCSV ? L".csv" : L".txt");
319+
}
320+
}
321+
322+
dwLen = GetWindowTextLengthW(g_hwndResult);
323+
if (dwLen == 0) return;
324+
325+
wbuf = (wchar_t*)LocalAlloc(LMEM_FIXED, (dwLen + 1) * sizeof(wchar_t));
326+
buf = (char*)LocalAlloc(LMEM_FIXED, (dwLen * 2) + 1);
327+
if (wbuf && buf) {
328+
GetWindowTextW(g_hwndResult, wbuf, dwLen + 1);
329+
330+
if (isCSV) {
331+
/* Convert tabs to commas, handle quoting */
332+
bp = buf;
333+
wp = wbuf;
334+
while (*wp) {
335+
if (*wp == '\t') {
336+
*bp++ = ',';
337+
wp++;
338+
} else if (*wp == '\r') {
339+
wp++;
340+
} else if (*wp == '\n') {
341+
*bp++ = '\r';
342+
*bp++ = '\n';
343+
wp++;
344+
} else {
345+
wchar_t *fieldStart = wp;
346+
needQuote = 0;
347+
while (*wp && *wp != '\t' && *wp != '\r' && *wp != '\n') {
348+
if (*wp == ',' || *wp == '"') needQuote = 1;
349+
wp++;
350+
}
351+
if (needQuote) {
352+
*bp++ = '"';
353+
while (fieldStart < wp) {
354+
if (*fieldStart == '"') *bp++ = '"';
355+
*bp++ = (char)*fieldStart++;
356+
}
357+
*bp++ = '"';
358+
} else {
359+
while (fieldStart < wp) *bp++ = (char)*fieldStart++;
360+
}
361+
}
362+
}
363+
*bp = '\0';
364+
dwLen = (DWORD)(bp - buf);
365+
} else {
366+
/* Plain text - just convert to ANSI */
367+
WideCharToMultiByte(CP_ACP, 0, wbuf, -1, buf, (dwLen * 2) + 1, NULL, NULL);
368+
dwLen = (DWORD)strlen(buf);
369+
}
370+
371+
hFile = CreateFileW(szFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
372+
if (hFile != INVALID_HANDLE_VALUE) {
373+
WriteFile(hFile, buf, dwLen, &dwWritten, NULL);
374+
CloseHandle(hFile);
375+
}
376+
}
377+
if (wbuf) LocalFree(wbuf);
378+
if (buf) LocalFree(buf);
379+
}
380+
381+
/*============================================================================
382+
** Export Results to CSV (legacy, kept for File menu)
278383
**============================================================================*/
279384

280385
void DoExportCSV(void) {
@@ -391,6 +496,208 @@ void DoExportTxt(void) {
391496
if (buf) LocalFree(buf);
392497
}
393498

499+
/*============================================================================
500+
** Export Single Table to CSV
501+
**============================================================================*/
502+
503+
/* Table picker dialog state */
504+
static HWND g_hwndTblList;
505+
static char g_selectedTable[128];
506+
static char **g_tableList;
507+
static int g_tableCount;
508+
509+
static LRESULT CALLBACK TablePickerProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
510+
switch (msg) {
511+
case WM_INITDIALOG: {
512+
RECT rc;
513+
int i;
514+
wchar_t wname[128];
515+
SetWindowTextW(hwnd, L"Select Table");
516+
GetClientRect(hwnd, &rc);
517+
g_hwndTblList = CreateWindowW(L"LISTBOX", NULL,
518+
WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL | LBS_NOTIFY,
519+
10, 10, rc.right - 20, rc.bottom - 56,
520+
hwnd, (HMENU)101, g_hInst, NULL);
521+
for (i = 0; i < g_tableCount; i++) {
522+
MultiByteToWideChar(CP_ACP, 0, g_tableList[i], -1, wname, 128);
523+
SendMessageW(g_hwndTblList, LB_ADDSTRING, 0, (LPARAM)wname);
524+
}
525+
SendMessage(g_hwndTblList, LB_SETCURSEL, 0, 0);
526+
CreateWindowW(L"BUTTON", L"Export",
527+
WS_CHILD | WS_VISIBLE | BS_DEFPUSHBUTTON,
528+
10, rc.bottom - 36, 60, 26, hwnd, (HMENU)IDOK, g_hInst, NULL);
529+
CreateWindowW(L"BUTTON", L"Cancel",
530+
WS_CHILD | WS_VISIBLE,
531+
80, rc.bottom - 36, 60, 26, hwnd, (HMENU)IDCANCEL, g_hInst, NULL);
532+
SetFocus(g_hwndTblList);
533+
return FALSE;
534+
}
535+
case WM_COMMAND:
536+
if (LOWORD(wParam) == IDOK || (LOWORD(wParam) == 101 && HIWORD(wParam) == LBN_DBLCLK)) {
537+
int sel = (int)SendMessage(g_hwndTblList, LB_GETCURSEL, 0, 0);
538+
if (sel >= 0 && sel < g_tableCount) {
539+
strcpy(g_selectedTable, g_tableList[sel]);
540+
EndDialog(hwnd, IDOK);
541+
}
542+
} else if (LOWORD(wParam) == IDCANCEL) {
543+
EndDialog(hwnd, IDCANCEL);
544+
}
545+
return TRUE;
546+
case WM_CLOSE:
547+
EndDialog(hwnd, IDCANCEL);
548+
return TRUE;
549+
}
550+
return FALSE;
551+
}
552+
553+
static int PickTable(char *tblName) {
554+
DLGTEMPLATE dlg;
555+
char **results = NULL;
556+
int nRows = 0, nCols = 0, ret;
557+
558+
/* Get table list */
559+
sqlite_get_table(g_db,
560+
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
561+
&results, &nRows, &nCols, NULL);
562+
563+
if (!results || nRows < 1) {
564+
if (results) sqlite_free_table(results);
565+
MessageBoxW(g_hwndMain, L"No tables in database", L"Export Table", MB_OK);
566+
return 0;
567+
}
568+
569+
/* Store for dialog */
570+
g_tableList = &results[1]; /* Skip header */
571+
g_tableCount = nRows;
572+
g_selectedTable[0] = 0;
573+
574+
/* Create dialog */
575+
memset(&dlg, 0, sizeof(dlg));
576+
dlg.style = WS_POPUP | WS_CAPTION | WS_SYSMENU | DS_CENTER;
577+
dlg.cx = 120; dlg.cy = 100;
578+
579+
ret = DialogBoxIndirectW(g_hInst, &dlg, g_hwndMain, (DLGPROC)TablePickerProc);
580+
581+
sqlite_free_table(results);
582+
583+
if (ret == IDOK && g_selectedTable[0]) {
584+
strcpy(tblName, g_selectedTable);
585+
return 1;
586+
}
587+
return 0;
588+
}
589+
590+
void DoExportTable(void) {
591+
CE_OPENFILENAME ofn;
592+
wchar_t szFile[MAX_PATH];
593+
char tblName[128];
594+
char sql[512];
595+
char **result;
596+
int nRow, nCol, i, j;
597+
HANDLE hFile;
598+
DWORD written;
599+
char line[4096];
600+
char *lp, *p;
601+
const char *t;
602+
603+
if (!g_db) return;
604+
605+
/* Get table name from schema selection or picker */
606+
tblName[0] = 0;
607+
if (g_viewMode == 2 && g_hwndSchema) {
608+
HTREEITEM hItem = TreeView_GetSelection(g_hwndSchema);
609+
if (hItem) {
610+
TV_ITEMW item;
611+
wchar_t text[128];
612+
item.mask = TVIF_TEXT | TVIF_IMAGE;
613+
item.hItem = hItem;
614+
item.pszText = text;
615+
item.cchTextMax = 128;
616+
TreeView_GetItem(g_hwndSchema, &item);
617+
if (item.iImage == 1) { /* IMG_TABLE */
618+
wchar_t *wp = text;
619+
while (*wp && *wp != ' ' && *wp != '(') wp++;
620+
*wp = 0;
621+
WideCharToMultiByte(CP_ACP, 0, text, -1, tblName, 128, NULL, NULL);
622+
}
623+
}
624+
}
625+
626+
/* If no table selected, show picker */
627+
if (!tblName[0]) {
628+
if (!PickTable(tblName)) return;
629+
}
630+
631+
/* Default filename from table name */
632+
MultiByteToWideChar(CP_ACP, 0, tblName, -1, szFile, MAX_PATH);
633+
lstrcatW(szFile, L".csv");
634+
635+
memset(&ofn, 0, sizeof(ofn));
636+
ofn.lStructSize = sizeof(ofn);
637+
ofn.hwndOwner = g_hwndMain;
638+
ofn.lpstrFile = szFile;
639+
ofn.nMaxFile = MAX_PATH;
640+
ofn.lpstrFilter = L"CSV Files (*.csv)\0*.csv\0All Files (*.*)\0*.*\0";
641+
ofn.lpstrDefExt = L"csv";
642+
ofn.lpstrTitle = L"Export Table";
643+
ofn.Flags = OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
644+
645+
if (!GetSaveFileNameW(&ofn)) return;
646+
647+
/* Query table data */
648+
p = sql;
649+
for (t = "SELECT * FROM \""; *t; ) *p++ = *t++;
650+
for (t = tblName; *t; ) *p++ = *t++;
651+
*p++ = '"'; *p = 0;
652+
653+
if (sqlite_get_table(g_db, sql, &result, &nRow, &nCol, NULL) != SQLITE_OK) return;
654+
655+
hFile = CreateFileW(szFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
656+
if (hFile == INVALID_HANDLE_VALUE) {
657+
sqlite_free_table(result);
658+
return;
659+
}
660+
661+
/* Write header row */
662+
lp = line;
663+
for (j = 0; j < nCol; j++) {
664+
char *val = result[j];
665+
if (j > 0) *lp++ = ',';
666+
if (val) while (*val) *lp++ = *val++;
667+
}
668+
*lp++ = '\r'; *lp++ = '\n';
669+
WriteFile(hFile, line, (DWORD)(lp - line), &written, NULL);
670+
671+
/* Write data rows */
672+
for (i = 1; i <= nRow; i++) {
673+
lp = line;
674+
for (j = 0; j < nCol; j++) {
675+
char *val = result[i * nCol + j];
676+
int needQuote = 0;
677+
char *v;
678+
if (j > 0) *lp++ = ',';
679+
if (val) {
680+
for (v = val; *v; v++) if (*v == ',' || *v == '"' || *v == '\n') needQuote = 1;
681+
if (needQuote) {
682+
*lp++ = '"';
683+
for (v = val; *v; v++) {
684+
if (*v == '"') *lp++ = '"';
685+
*lp++ = *v;
686+
}
687+
*lp++ = '"';
688+
} else {
689+
while (*val) *lp++ = *val++;
690+
}
691+
}
692+
}
693+
*lp++ = '\r'; *lp++ = '\n';
694+
WriteFile(hFile, line, (DWORD)(lp - line), &written, NULL);
695+
}
696+
697+
CloseHandle(hFile);
698+
sqlite_free_table(result);
699+
}
700+
394701
/*============================================================================
395702
** Export Table to CSV (helper for DoExportDb)
396703
**============================================================================*/

0 commit comments

Comments
 (0)