diff --git a/src/dialogs.cpp b/src/dialogs.cpp index d27de79..23450a2 100644 --- a/src/dialogs.cpp +++ b/src/dialogs.cpp @@ -1346,7 +1346,7 @@ namespace dialogs { sqlite3_stmt *stmt; char query8[2048]; snprintf(query8, 2047, "select count(1) from \"%s\".\"%s\"", schema8, tablename8); - //MessageBoxA(hWnd, query8, 0, 0); + if (SQLITE_OK == sqlite3_prepare_v2(db, query8, -1, &stmt, 0)) { sqlite3_step(stmt); TCHAR count16[32]; @@ -1919,19 +1919,10 @@ namespace dialogs { if (cmd == IDM_VALUE_VIEW) { int rowNo = (int)(LONG_PTR)GetProp(hWnd, TEXT("CURRENTROW")); int colNo = (int)(LONG_PTR)GetProp(hWnd, TEXT("CURRENTCOLUMN")); - int colCount = ListView_GetColumnCount(hListWnd); - int no = colNo + resultset[rowNo] * (colCount - 1); - TCHAR* text16 = cache[resultset[rowNo]][colNo]; - - if (datatypes[no] == SQLITE_NULL) - return 0; - TDlgValueParam data = {0}; - if (datatypes[no] == SQLITE_BLOB) - data = {SQLITE_BLOB, blobs[no]}; - else - data = {SQLITE_TEXT, (const unsigned char*)text16}; - openDialog(IDD_VALUE_VIEWER, (DLGPROC)dialogs::cbDlgValueViewer, (LPARAM)&data); + TValue value = {0}; + if (ListView_GetItemValue(hListWnd, rowNo, colNo, &value)) + openDialog(IDD_VALUE_VIEWER, (DLGPROC)cbDlgValueViewer, (LPARAM)&value); } if (cmd == IDM_VALUE_EDIT) { @@ -2525,11 +2516,17 @@ namespace dialogs { SetProp(hPreviewWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hPreviewWnd, GWLP_WNDPROC, (LONG_PTR)&cbNewValueViewer)); SetProp(hPreviewWnd, TEXT("INFO"), (HANDLE)(new TCHAR[255])); SetProp(hPreviewWnd, TEXT("EXT"), (HANDLE)(new TCHAR[32])); + + TValue* value = (TValue*)lParam; + SetProp(hPreviewWnd, TEXT("DATA"), (HANDLE)value->data); + SetProp(hPreviewWnd, TEXT("DATATYPE"), IntToPtr(value->dataType)); + SetProp(hPreviewWnd, TEXT("DATALEN"), IntToPtr(value->dataLen)); + SendMessage(hPreviewWnd, WM_SETFONT, (LPARAM)hFont, 0); CreateWindowEx(WS_EX_TOPMOST, WC_STATIC, NULL, WS_VISIBLE | WS_CHILD | SS_CENTER | SS_CENTERIMAGE | SS_NOTIFY, 20, 20, 100, 100, hPreviewWnd, (HMENU)IDC_PREVIEW_INFO, GetModuleHandle(0), NULL); - TDlgValueParam* param = (TDlgValueParam*)lParam; - PostMessage(hPreviewWnd, WMU_UPDATE_PREVIEW, (WPARAM)param->dataType, (LPARAM)param->data); + SetWindowLongPtr(hPreviewWnd, GWLP_USERDATA, lParam); + PostMessage(hPreviewWnd, WMU_UPDATE_PREVIEW, 0, 0); utils::alignDialog(hWnd, hMainWnd); } @@ -2563,6 +2560,7 @@ namespace dialogs { return false; } + // USERDATA = TValue LRESULT CALLBACK cbNewValueViewer (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_SIZE: { @@ -2616,8 +2614,9 @@ namespace dialogs { if (cmd == IDM_PREVIEW_TO_FILE || cmd == IDM_PREVIEW_AS_FILE) { TCHAR* ext16 = (TCHAR*)GetProp(hWnd, TEXT("EXT")); - unsigned char* data = (unsigned char*)GetProp(hWnd, TEXT("DATA")); + const unsigned char* data = (const unsigned char*)GetProp(hWnd, TEXT("DATA")); int dataType = PtrToInt(GetProp(hWnd, TEXT("DATATYPE"))); + int dataLen = PtrToInt(GetProp(hWnd, TEXT("DATALEN"))); TCHAR path16[MAX_PATH + 1] {0}; if (cmd == IDM_PREVIEW_TO_FILE) { @@ -2632,7 +2631,7 @@ namespace dialogs { if (dataType == SQLITE_BLOB) { FILE* f = _tfopen(path16, TEXT("wb")); - fwrite(data + 4, utils::getBlobSize(data) - 4, 1, f); + fwrite(data, dataLen, 1, f); fclose(f); } else { FILE* f = _tfopen(path16, TEXT("wb, ccs=UTF-8")); @@ -2653,7 +2652,7 @@ namespace dialogs { if (cmd == IDM_PREVIEW_SWITCH_PLUGIN) { SetProp(hWnd, TEXT("SWITCH"), IntToPtr(1)); - SendMessage(hWnd, WMU_UPDATE_PREVIEW, (WPARAM)GetProp(hWnd, TEXT("DATATYPE")), (LPARAM)GetProp(hWnd, TEXT("DATA"))); + SendMessage(hWnd, WMU_UPDATE_PREVIEW, 0, 0); } } break; @@ -2668,37 +2667,32 @@ namespace dialogs { if(!GetProp(hWnd, TEXT("SWITCH"))) RemoveProp(hWnd, TEXT("PLUGINNO")); - RemoveProp(hWnd, TEXT("DATA")); - RemoveProp(hWnd, TEXT("DATATYPE")); - HWND hInfoWnd = GetDlgItem(hWnd, IDC_PREVIEW_INFO); ShowWindow(hInfoWnd, SW_HIDE); } break; - // wParam = SQLite type, lParam = data + // wParam = cell case WMU_UPDATE_PREVIEW: { SendMessage(hWnd, WMU_RESET_PREVIEW, 0, 0); - SetProp(hWnd, TEXT("DATATYPE"), (HANDLE)wParam); - SetProp(hWnd, TEXT("DATA"), (HANDLE)lParam); + bool isSwitch = PtrToInt(GetProp(hWnd, TEXT("SWITCH"))); RemoveProp(hWnd, TEXT("SWITCH")); - int dataType = wParam; HWND hInfoWnd = GetDlgItem(hWnd, IDC_PREVIEW_INFO); ShowWindow(hInfoWnd, SW_HIDE); + const unsigned char* data = (const unsigned char*)GetProp(hWnd, TEXT("DATA")); + int dataType = PtrToInt(GetProp(hWnd, TEXT("DATATYPE"))); + int dataLen = PtrToInt(GetProp(hWnd, TEXT("DATALEN"))); + if (dataType == SQLITE_NULL) { SetWindowText(hWnd, TEXT("The value is NULL")); return 0; } - const unsigned char* blob = (const unsigned char*)lParam; - TCHAR* text = (TCHAR*)lParam; TCHAR infoText16[255] = {0x25B2, 0}; - const unsigned char* data = dataType == SQLITE_BLOB ? blob + 4 : (const unsigned char *)text; - int dataLen = dataType == SQLITE_BLOB ? utils::getBlobSize(blob) - 4 : _tcslen(text); TCHAR* info16 = (TCHAR*)GetProp(hWnd, TEXT("INFO")); TCHAR* ext16 = (TCHAR*)GetProp(hWnd, TEXT("EXT")); @@ -4059,7 +4053,7 @@ namespace dialogs { HWND hStatusWnd = CreateStatusWindow(WS_CHILD | WS_VISIBLE, NULL, hWnd, IDC_DLG_STATUSBAR); int type = lParam; - const TCHAR* type16 = ADDON_TYPES16[type]; + const TCHAR* type16 = ADDON_EXTS16[type]; SetWindowLongPtr(hWnd, GWLP_USERDATA, type); sqlite3_exec(prefs::db, "create table if not exists temp.addons (name primary key, enable, version, installed_version, installed, author, homepage, brief, description)", 0, 0, 0); @@ -4073,7 +4067,9 @@ namespace dialogs { SHFileOperation(&fo); SHCreateDirectoryEx(0, uploadPath16, 0); - char* repository8 = type == ADDON_SQLITE_EXTENSION ? prefs::get("extension-repository", EXTENSION_REPOSITORY) : prefs::get("viewer-repository", VIEWER_REPOSITORY); + char* repository8 = type == ADDON_SQLITE_EXTENSION ? prefs::get("extension-repository", EXTENSION_REPOSITORY) : + type == ADDON_COLUMN_MODIFIER ? prefs::get("modifier-repository", MODIFIER_REPOSITORY) : + prefs::get("viewer-repository", VIEWER_REPOSITORY); SetProp(hWnd, TEXT("REPOSITORY8"), repository8); // Repo @@ -4082,7 +4078,7 @@ namespace dialogs { char* repo8 = utils::httpRequest("GET", "raw.githubusercontent.com", url8); char title8[1024]; - snprintf(title8, 1023, "%s - github.com/%s", type == ADDON_VALUE_VIEWER ? "Viewer plugin manager" : "SQLite extension manager", repository8); + snprintf(title8, 1023, "%s - github.com/%s", type == ADDON_VALUE_VIEWER ? "Value viewer plugin manager" : type == ADDON_COLUMN_MODIFIER ? "Column modifier plugin manager" : "SQLite extension manager", repository8); TCHAR* title16 = utils::utf8to16(title8); SetWindowText(hWnd, title16); delete [] title16; @@ -4229,7 +4225,7 @@ namespace dialogs { case WM_NOTIFY: { NMHDR* pHdr = (LPNMHDR)lParam; int type = GetWindowLongPtr(hWnd, GWLP_USERDATA); - const TCHAR* type16 = ADDON_TYPES16[type]; + const TCHAR* type16 = ADDON_EXTS16[type]; if (pHdr->code == (DWORD)LVN_ITEMCHANGING && pHdr->idFrom == IDC_DLG_ADDON_LIST && IsWindowVisible(hWnd)) { NMLISTVIEW* pLV = (NMLISTVIEW*)lParam; @@ -4507,6 +4503,7 @@ namespace dialogs { TCHAR* name16 = utils::utf8to16((const char*)sqlite3_column_text(stmt, 1)); int no = ListBox_AddString(hListWnd, name16); ListBox_SetItemData(hListWnd, no, sqlite3_column_int(stmt, 0)); + delete [] name16; } } sqlite3_finalize(stmt); @@ -5148,6 +5145,21 @@ namespace dialogs { DeleteObject(hNullBrush); } + if (type == CHART_LINES || type == CHART_DOTS || type == CHART_AREAS || type == CHART_HISTOGRAM) { + double zoom = PtrToInt(GetProp(hChartWnd, TEXT("ZOOM"))) / 10.0; + minX /= zoom; + maxX /= zoom; + + RECT rc{0}; + GetClientRect(hChartWnd, &rc); + int w = rc.right; + double offsetX = (double)PtrToInt(GetProp(hChartWnd, TEXT("POSITION"))); + offsetX = map(offsetX, 0, w - 2 * CHART_BORDER, 0, maxX - minX); + + minX -= offsetX; + maxX -= offsetX; + } + // Grid if (type == CHART_LINES || type == CHART_DOTS || type == CHART_AREAS || type == CHART_HISTOGRAM) { HPEN hPen = CreatePen(PS_SOLID, 1, RGB(200, 200, 200)); @@ -5188,8 +5200,10 @@ namespace dialogs { _tcsftime(val, 64, d < DAY ? TEXT("%Y-%m-%d %H:%M") : TEXT("%Y-%m-%d"), &ts); RECT rc {x - 30, 10, x + 30, CHART_BORDER + 5}; + DrawText(hdc, val, _tcslen(val), &rc, DT_BOTTOM | DT_WORDBREAK | DT_CENTER | DT_CALCRECT); DrawText(hdc, val, _tcslen(val), &rc, DT_BOTTOM | DT_WORDBREAK | DT_CENTER); RECT rc2 {x - 30, h - CHART_BORDER + 10, x + 30, h}; + DrawText(hdc, val, _tcslen(val), &rc2, DT_TOP | DT_WORDBREAK | DT_CENTER | DT_CALCRECT); DrawText(hdc, val, _tcslen(val), &rc2, DT_TOP | DT_WORDBREAK | DT_CENTER); } } else { @@ -5208,8 +5222,11 @@ namespace dialogs { _sntprintf(val, 63, TEXT("%g"), x0); RECT rc {x - 30, 0, x + 30, CHART_BORDER - 5}; + DrawText(hdc, val, _tcslen(val), &rc, DT_BOTTOM | DT_SINGLELINE | DT_CENTER | DT_CALCRECT); DrawText(hdc, val, _tcslen(val), &rc, DT_BOTTOM | DT_SINGLELINE | DT_CENTER); + RECT rc2 {x - 30, h - CHART_BORDER + 5, x + 30, h}; + DrawText(hdc, val, _tcslen(val), &rc2, DT_TOP | DT_SINGLELINE | DT_CENTER | DT_CALCRECT); DrawText(hdc, val, _tcslen(val), &rc2, DT_TOP | DT_SINGLELINE | DT_CENTER); } } @@ -5238,6 +5255,7 @@ namespace dialogs { DeleteObject(hPen); } + // Graph int lineNo = 0; if (type == CHART_LINES || type == CHART_DOTS) { for (int colNo = 1; colNo < colCount; colNo++) { @@ -5254,14 +5272,17 @@ namespace dialogs { int pointCount = 0; for (int rowNo = 0; rowNo < rowCount; rowNo++) { - if (data[colNo + rowNo * colCount] == CHART_NULL) + double valX = data[colBase + rowNo * colCount]; + double valY = data[colNo + rowNo * colCount]; + + if (valX == CHART_NULL || valY == CHART_NULL) continue; - if (data[colBase + rowNo * colCount] == CHART_NULL) + if (valX < minX || valX > maxX) continue; - double x = map(data[colBase + rowNo * colCount], minX, maxX, CHART_BORDER, w - CHART_BORDER); - double y = h - map(data[colNo + rowNo * colCount], minY, maxY, CHART_BORDER, h - CHART_BORDER); + double x = map(valX, minX, maxX, CHART_BORDER, w - CHART_BORDER); + double y = h - map(valY, minY, maxY, CHART_BORDER, h - CHART_BORDER); if (type == CHART_LINES) { if (!pointCount) { @@ -5668,6 +5689,8 @@ namespace dialogs { float d = ceil(log10(maxY - minY)); minmax[2] = floor(minY/d) * d; minmax[3] = ceil(maxY/d) * d; + + SendMessage(GetDlgItem(hWnd, IDC_DLG_CHART), WM_COMMAND, IDM_CHART_RESET, 0); } break; @@ -6175,14 +6198,21 @@ namespace dialogs { char* viewerRepository8 = prefs::get("viewer-repository", VIEWER_REPOSITORY); TCHAR* viewerRepository16 = utils::utf8to16(viewerRepository8); SetDlgItemText(hTabWnd, IDC_DLG_VIEWER_REPOSITORY, viewerRepository16); - Edit_SetCueBannerText(GetDlgItem(hTabWnd, IDC_DLG_VIEWER_REPOSITORY), viewerRepository16); + Edit_SetCueBannerText(GetDlgItem(hTabWnd, IDC_DLG_VIEWER_REPOSITORY), TEXT(VIEWER_REPOSITORY)); delete [] viewerRepository16; delete [] viewerRepository8; + char* modifierRepository8 = prefs::get("modifier-repository", MODIFIER_REPOSITORY); + TCHAR* modifierRepository16 = utils::utf8to16(modifierRepository8); + SetDlgItemText(hTabWnd, IDC_DLG_MODIFIER_REPOSITORY, modifierRepository16); + Edit_SetCueBannerText(GetDlgItem(hTabWnd, IDC_DLG_MODIFIER_REPOSITORY), TEXT(MODIFIER_REPOSITORY)); + delete [] modifierRepository16; + delete [] modifierRepository8; + char* extensionRepository8 = prefs::get("extension-repository", EXTENSION_REPOSITORY); TCHAR* extensionRepository16 = utils::utf8to16(extensionRepository8); SetDlgItemText(hTabWnd, IDC_DLG_EXTENSION_REPOSITORY, extensionRepository16); - Edit_SetCueBannerText(GetDlgItem(hTabWnd, IDC_DLG_EXTENSION_REPOSITORY), extensionRepository16); + Edit_SetCueBannerText(GetDlgItem(hTabWnd, IDC_DLG_EXTENSION_REPOSITORY), TEXT(EXTENSION_REPOSITORY)); delete [] extensionRepository16; delete [] extensionRepository8; @@ -6191,6 +6221,11 @@ namespace dialogs { ComboBox_AddString(hIndent, INDENT_LABELS[i]); ComboBox_SetCurSel(hIndent, prefs::get("editor-indent")); + HWND hDelimiter = GetDlgItem(hTabWnd, IDC_DLG_DELIMITER); + for (int i = 0; i < 4; i++) + ComboBox_AddString(hDelimiter, i != 2 ? tools::DELIMITERS[i] : TEXT("Tab")); + ComboBox_SetCurSel(hDelimiter, prefs::get("copy-to-clipboard-delimiter")); + HBRUSH* brushes = new HBRUSH[SETTING_COLOR_COUNT]{0}; brushes[0] = CreateSolidBrush(prefs::get("color-keyword")); brushes[1] = CreateSolidBrush(prefs::get("color-function")); @@ -6240,6 +6275,7 @@ namespace dialogs { prefs::set("editor-indent", ComboBox_GetCurSel(GetDlgItem(hTabWnd, IDC_DLG_INDENT))); prefs::set("use-foreign-keys", Button_GetCheck(GetDlgItem(hTabWnd, IDC_DLG_FOREIGN_KEYS))); prefs::set("use-legacy-rename", Button_GetCheck(GetDlgItem(hTabWnd, IDC_DLG_LEGACY_RENAME))); + prefs::set("copy-to-clipboard-delimiter", ComboBox_GetCurSel(GetDlgItem(hTabWnd, IDC_DLG_DELIMITER))); GetDlgItemText(hTabWnd, IDC_DLG_ROW_LIMIT, buf, 255); prefs::set("row-limit", (int)_tcstod(buf, NULL)); @@ -6269,6 +6305,11 @@ namespace dialogs { prefs::set("viewer-repository", viewerRepository8); delete [] viewerRepository8; + GetDlgItemText(hTabWnd, IDC_DLG_MODIFIER_REPOSITORY, buf, 254); + char* modifierRepository8 = utils::utf16to8(_tcslen(buf) ? buf : TEXT(MODIFIER_REPOSITORY)); + prefs::set("modifier-repository", modifierRepository8); + delete [] modifierRepository8; + GetDlgItemText(hTabWnd, IDC_DLG_EXTENSION_REPOSITORY, buf, 254); char* extensionRepository8 = utils::utf16to8(_tcslen(buf) ? buf : TEXT(EXTENSION_REPOSITORY)); prefs::set("extension-repository", extensionRepository8); @@ -6321,12 +6362,14 @@ namespace dialogs { IDC_DLG_COLOR + 6, IDC_DLG_COLOR + 7, IDC_DLG_COLOR + 8, IDC_DLG_COLOR + 9, IDC_DLG_COLOR + 10, IDC_DLG_COLOR + 11, IDC_DLG_ESCAPE_LABEL, IDC_DLG_EXIT_BY_ESCAPE, IDC_DLG_INDENT_LABEL, IDC_DLG_INDENT, IDC_DLG_ROW_LIMIT_LABEL, IDC_DLG_ROW_LIMIT, IDC_DLG_CLI_ROW_LIMIT_LABEL, IDC_DLG_CLI_ROW_LIMIT, - IDC_DLG_BEEP_LABEL, IDC_DLG_BEEP_ON_QUERY_END + IDC_DLG_BEEP_LABEL, IDC_DLG_BEEP_ON_QUERY_END, + IDC_DLG_DELIMITER_LABEL, IDC_DLG_DELIMITER }, { IDC_DLG_STARTUP, IDC_DLG_STARTUP_LABEL, IDC_DLG_FOREIGN_KEYS, IDC_DLG_LEGACY_RENAME, IDC_DLG_GOOGLE_KEY_LABEL, IDC_DLG_GOOGLE_KEY, IDC_DLG_VIEWER_REPOSITORY, IDC_DLG_VIEWER_REPOSITORY_LABEL, + IDC_DLG_MODIFIER_REPOSITORY, IDC_DLG_MODIFIER_REPOSITORY_LABEL, IDC_DLG_EXTENSION_REPOSITORY, IDC_DLG_EXTENSION_REPOSITORY_LABEL } }; @@ -7215,30 +7258,35 @@ namespace dialogs { switch (msg) { case WM_DESTROY: { RemoveProp(hWnd, TEXT("SCROLLY")); - } - break; - - case WM_ERASEBKGND: { - RECT rc{0}; - GetClientRect(hWnd, &rc); - FillRect((HDC)wParam, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH)); - - return true; + RemoveProp(hWnd, TEXT("ZOOM")); + RemoveProp(hWnd, TEXT("POSITION")); } break; case WM_PAINT : { - InvalidateRect(hWnd, NULL, true); + InvalidateRect(hWnd, NULL, TRUE); RECT rc{0}; GetClientRect(hWnd, &rc); int w = rc.right; int h = rc.bottom; PAINTSTRUCT ps{0}; - ps.fErase = true; + ps.fErase = FALSE; HDC hdc = BeginPaint(hWnd, &ps); - drawChart(hWnd, hdc, w, h, (int)(LONG_PTR)GetProp(hWnd, TEXT("SCROLLY"))); + // Double buffering https://stackoverflow.com/a/25461603/6121703 + HDC memDC = CreateCompatibleDC(hdc); + HBITMAP hBmp = CreateCompatibleBitmap(hdc, w, h); + HBITMAP hOldBmp = (HBITMAP)SelectObject(memDC, hBmp); + + FillRect(memDC, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH)); + drawChart(hWnd, memDC, w, h, (int)(LONG_PTR)GetProp(hWnd, TEXT("SCROLLY"))); + BitBlt(hdc, 0, 0, w, h, memDC, 0, 0, SRCCOPY); + + SelectObject(memDC, hOldBmp); + DeleteObject(hBmp); + DeleteDC(memDC); + EndPaint(hWnd, &ps); return true; @@ -7291,19 +7339,41 @@ namespace dialogs { ReleaseDC(hWnd, hDC); DeleteObject(hBitmap); } + + if (wParam == IDM_CHART_RESET) { + SetProp(hWnd, TEXT("ZOOM"), IntToPtr(10)); + SetProp(hWnd, TEXT("POSITION"), 0); + InvalidateRect(hWnd, NULL, TRUE); + } } break; case WM_LBUTTONDOWN: { + SetProp(hWnd, TEXT("LAST_POSITION"), IntToPtr(GET_X_LPARAM(lParam))); + SetCapture(hWnd); SetFocus(hWnd); return true; } break; + case WM_LBUTTONUP: { + ReleaseCapture(); + return true; + } + break; + case WM_MOUSEMOVE: { HWND hParentWnd = GetParent(hWnd); - int type = (int)(LONG_PTR)GetProp(hParentWnd, TEXT("TYPE")); + + if (wParam & MK_LBUTTON) { + int pos = PtrToInt(GetProp(hWnd, TEXT("POSITION"))); + int delta = PtrToInt(GetProp(hWnd, TEXT("LAST_POSITION"))) - GET_X_LPARAM(lParam); + SetProp(hWnd, TEXT("POSITION"), IntToPtr(pos - delta)); + SetProp(hWnd, TEXT("LAST_POSITION"), IntToPtr(GET_X_LPARAM(lParam))); + InvalidateRect(hWnd, NULL, TRUE); + } + if (type == CHART_LINES || type == CHART_DOTS || type == CHART_AREAS || type == CHART_HISTOGRAM) { int* colTypes = (int*)GetProp(hParentWnd, TEXT("COLTYPES")); int colBase = (int)(LONG_PTR)GetProp(hParentWnd, TEXT("COLBASE")); @@ -7320,9 +7390,14 @@ namespace dialogs { int w = rc.right; int h = rc.bottom; + double zoom = PtrToInt(GetProp(hWnd, TEXT("ZOOM"))) / 10.0; + minX /= zoom; + maxX /= zoom; + int offsetX = PtrToInt(GetProp(hWnd, TEXT("POSITION"))); + TCHAR title[255]{0}; - double x = LOWORD(lParam); - double y = h - HIWORD(lParam); + double x = GET_X_LPARAM(lParam) - offsetX; + double y = h - GET_Y_LPARAM(lParam); x = map(x, CHART_BORDER, w - CHART_BORDER, minX, maxX); y = map(y, CHART_BORDER, h - CHART_BORDER, minY, maxY); @@ -7340,6 +7415,38 @@ namespace dialogs { } break; + case WM_MOUSEWHEEL: { + HWND hParentWnd = GetParent(hWnd); + + int type = (int)(LONG_PTR)GetProp(hParentWnd, TEXT("TYPE")); + bool isUp = GET_WHEEL_DELTA_WPARAM(wParam) > 0; + if (type == CHART_BARS) { + int action = isUp ? SB_LINEUP : SB_LINEDOWN; + SendMessage(hWnd, WM_VSCROLL, MAKELPARAM(action, 0), 0); + } else { + RECT rc{0}; + GetClientRect(hWnd, &rc); + int w = rc.right; + + POINT p = {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)}; + ScreenToClient(hWnd, &p); + double aspect = (double)(p.x - CHART_BORDER) / (w - 2.0 * CHART_BORDER); + + int zoom = PtrToInt(GetProp(hWnd, TEXT("ZOOM"))); + int pos = PtrToInt(GetProp(hWnd, TEXT("POSITION"))); + + int prevZoom = zoom; + zoom = MAX(zoom + (isUp ? 2 : -2), 10); + pos -= (w - 2.0 * CHART_BORDER) * (zoom - prevZoom) / 10. * aspect; + + SetProp(hWnd, TEXT("ZOOM"), IntToPtr(zoom)); + SetProp(hWnd, TEXT("POSITION"), IntToPtr(pos)); + + InvalidateRect(hWnd, NULL, TRUE); + } + } + break; + case WM_VSCROLL: { WORD action = LOWORD(wParam); int scrollY = (int)(LONG_PTR)GetProp(hWnd, TEXT("SCROLLY")); @@ -7397,25 +7504,15 @@ namespace dialogs { si.nTrackPos = 0; SetScrollInfo(hWnd, SB_VERT, &si, true); SetProp(hWnd, TEXT("SCROLLY"), 0); + } else { + PostMessage(hWnd, WM_COMMAND, IDM_CHART_RESET, 0); } - InvalidateRect(hWnd, 0, true); + InvalidateRect(hWnd, 0, TRUE); return true; } break; - - case WM_MOUSEWHEEL: { - HWND hParentWnd = GetParent(hWnd); - - int type = (int)(LONG_PTR)GetProp(hParentWnd, TEXT("TYPE")); - if (type != CHART_BARS) - return true; - - int action = GET_WHEEL_DELTA_WPARAM(wParam) > 0 ? SB_LINEUP : SB_LINEDOWN; - SendMessage(hWnd, WM_VSCROLL, MAKELPARAM(action, 0), 0); - } - break; } return CallWindowProc(DefWindowProc, hWnd, msg, wParam, lParam); @@ -7548,7 +7645,6 @@ namespace dialogs { PostMessage(hChartWnd, WM_SIZE, 0, MAKELPARAM(rc.right, rc.bottom)); } } - } break; } diff --git a/src/global.h b/src/global.h index 9563653..dbf0218 100644 --- a/src/global.h +++ b/src/global.h @@ -14,6 +14,8 @@ #define MAX_RESULT_COLUMN_COUNT 96 #define MAX_TRANSPOSE_ROWS 512 #define MAX_PLUGIN_COUNT 128 +#define MAX_MODIFIER_OUTPUT_LEN 1024 +#define MAX_MODIFIER_ERROR_LEN 255 #define DLG_OK 1 #define DLG_CANCEL -1 @@ -27,6 +29,7 @@ #define ACTION_UPDATETAB 6 #define ACTION_REDRAW 7 #define ACTION_SET_THEME 8 +#define ACTION_RESET_MODIFIERS 9 #define LOGGER_HIGHLIGHT 1 #define LOGGER_OCCURRENCE 2 @@ -45,7 +48,7 @@ #define ADDON_SQLITE_EXTENSION 0 #define ADDON_VALUE_VIEWER 1 -#define ADDON_VALUE_FORMAT 2 +#define ADDON_COLUMN_MODIFIER 2 #define EDITOR_HIGHLIGHT TEXT("HIGHLIGHT") #define EDITOR_PARENTHESIS TEXT("PARENTHESIS") @@ -85,13 +88,51 @@ extern const char *TYPES8[6]; extern const TCHAR *TYPES16[6]; extern const TCHAR *TYPES16u[6]; extern const TCHAR *TYPES16p[6]; -extern const TCHAR* ADDON_TYPES16[3]; +extern const TCHAR* ADDON_EXTS16[3]; extern COLORREF GRIDCOLORS[8]; extern HFONT hFont; extern HFONT hMenuFont; extern HIMAGELIST hIconsImageList; +typedef HWND (WINAPI *pluginView)(HWND hPreviewWnd, const unsigned char* data, int dataLen, int dataType, TCHAR* outInfo16, TCHAR* outExtension16); +typedef int (WINAPI* pluginClose)(HWND hPluginWnd); +typedef BOOL (WINAPI* pluginActivate)(HWND hListWnd, int colNo, TCHAR* err16); +typedef BOOL (WINAPI* pluginDeactivate)(HWND hListWnd, int colNo); +typedef BOOL (WINAPI *pluginSetText)(HWND hListWnd, int colNo, const unsigned char* data, int dataLen, int dataType, TCHAR* output16); +typedef BOOL (WINAPI *pluginSetColor)(NMLVCUSTOMDRAW* pCustomDraw, const unsigned char* data, int dataLen, int dataType); +typedef BOOL (WINAPI *pluginRender)(NMLVCUSTOMDRAW* pCustomDraw, const unsigned char* data, int dataLen, int dataType); +typedef int (WINAPI* pluginGetPriority)(); + +struct TPlugin { + HMODULE hModule; + pluginView view; + pluginClose close; + pluginActivate activate; + pluginDeactivate deactivate; + pluginSetText setText; + pluginSetColor setColor; + pluginRender render; + int type; + int priority; + TCHAR name[256]; +}; +extern TPlugin plugins[MAX_PLUGIN_COUNT + 1]; + +typedef struct TDlgParam { + const TCHAR* s1; + const TCHAR* s2; + const TCHAR* s3; + const TCHAR* s4; +} TDlgParam; + +typedef struct TValue { + const unsigned char* data; + int dataLen; + int dataType; + LONG_PTR extra; +} TValue; + LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewEditor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); @@ -124,8 +165,11 @@ void logger(ULONG_PTR type, TCHAR* msg16); int Toolbar_SetButtonState(HWND hToolbar, int id, byte state, LPARAM lParam = 0); DWORD_PTR Toolbar_GetButtonData(HWND hToolbar, int id); +LPARAM TreeView_GetItemParam (HWND hTreeWnd, HTREEITEM hItem); int ListView_SetData(HWND hListWnd, sqlite3_stmt *stmt, bool isRef = false); int ListView_ShowRef(HWND hListWnd, int rowNo, int colNo); +BOOL ListView_GetItemValue(HWND hListWnd, int rowNo, int colNo, TValue* cell, bool ignoreResultset = false); +TCHAR* ListView_GetItemValueText(HWND hListWnd, int rowNo, int colNo, bool ignoreResultset = false); int ListView_Sort(HWND hListWnd, int colNo); int ListView_Reset(HWND hListWnd); int ListView_GetColumnCount(HWND hListWnd); @@ -138,36 +182,12 @@ BOOL Menu_SetItemStateByPosition(HMENU hMenu, UINT pos, UINT fState); BOOL Menu_InsertItem(HMENU hMenu, UINT uPosition, UINT wID, UINT fState, const TCHAR* pszText); BOOL Menu_SetData(HMENU hMenu, ULONG_PTR data); ULONG_PTR Menu_GetData(HMENU hMenu); +BOOL Menu_SetItemData(HMENU hMenu, UINT wID, ULONG_PTR lParam); +ULONG_PTR Menu_GetItemData(HMENU hMenu, UINT wID); COLORREF RichEdit_GetTextColor (HWND hWnd, int pos); int TabCtrl_GetItemText(HWND hWnd, int iItem, TCHAR* pszText, int cchTextMax); LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignoreLastColumn = false); TCHAR* getDDL(const TCHAR* schema16, const TCHAR* name16, int type, bool withDrop = false); bool showDbError(HWND hWnd); - -typedef HWND (WINAPI *pluginView)(HWND hPreviewWnd, const unsigned char* data, int dataLen, int dataType, TCHAR* outInfo16, TCHAR* outExtension16); -typedef int (WINAPI* pluginClose)(HWND hPluginWnd); -typedef int (WINAPI* pluginGetPriority)(); -struct TPlugin { - HMODULE hModule; - pluginView view; - pluginClose close; - int type; - int priority; -}; -extern TPlugin plugins[MAX_PLUGIN_COUNT]; - -typedef struct TDlgParam { - const TCHAR* s1; - const TCHAR* s2; - const TCHAR* s3; - const TCHAR* s4; -} TDlgParam; - -typedef struct TDlgValueParam { - int dataType; - const unsigned char* data; - LONG_PTR extra; -} TDlgValueParam; - #endif diff --git a/src/main.cpp b/src/main.cpp index b93a6c1..25349a7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,7 +30,7 @@ const TCHAR *TYPES16[6] = {TEXT("current"), TEXT("table"), TEXT("view"), TEXT("i const TCHAR *TYPES16u[6] = {TEXT("CURRENT"), TEXT("TABLE"), TEXT("VIEW"), TEXT("INDEX"), TEXT("TRIGGER"), TEXT("COLUMN")}; const TCHAR *TYPES16p[6] = {TEXT(""), TEXT("Tables"), TEXT("Views"), TEXT("Indexes"), TEXT("Triggers"), TEXT("Columns")}; const TCHAR *transactionStates[] = {TEXT(""),TEXT(" TRN")}; -const TCHAR* ADDON_TYPES16[3] = {TEXT("dll"), TEXT("vvp"), TEXT("vfp")}; +const TCHAR* ADDON_EXTS16[3] = {TEXT("dll"), TEXT("vvp"), TEXT("cmp")}; COLORREF GRIDCOLORS[8]{0}; // 0 = current cell // AutoComplete @@ -43,13 +43,13 @@ TCHAR* TABLES[MAX_ENTITY_COUNT] = {0}; sqlite3 *db; -HWND hMainWnd = 0, hToolbarWnd, hEditorSearchWnd, hStatusWnd, hTreeWnd, hSchemaWnd, hEditorWnd, hTabWnd, hMainTabWnd, hLoggerWnd = 0, +HWND hMainWnd = 0, hToolbarWnd, hEditorSearchWnd, hStatusWnd, hTreeWnd, hTreeSearchWnd, hSchemaWnd, hEditorWnd, hTabWnd, hMainTabWnd, hLoggerWnd = 0, hTooltipWnd = 0, hAutoComplete, hAutoCompleteHelp, hDragWnd = 0, hDialog, hSortingResultWnd; -HMENU hMainMenu, hSchemaMenu, hDbMenu, hEditorMenu, hResultMenu, hTabResultMenu, hCliMenu, hPreviewMenu; +HMENU hMainMenu, hSchemaMenu, hDbMenu, hEditorMenu, hResultMenu, hModifierMenu, hTabResultMenu, hCliMenu, hPreviewMenu; HWND hDialogs[MAX_DIALOG_COUNT]{0}; HWND hEditors[MAX_DIALOG_COUNT + MAX_TAB_COUNT]{0}; -TPlugin plugins[MAX_PLUGIN_COUNT] = {0}; +TPlugin plugins[MAX_PLUGIN_COUNT + 1] = {0}; HTREEITEM treeItems[5]; // 0 - current TCHAR treeEditName[255]; @@ -121,7 +121,7 @@ bool attachOdbcDb(sqlite3* conn, const TCHAR* alias); bool isDbBusy(); void enableMainMenu(); void disableMainMenu(); -void reloadPlugins(); +void reloadPlugins(int type = 0); void updateExecuteMenu(bool isEnable); void updateRecentList(); void updateTables(const TCHAR* schema16); @@ -153,6 +153,7 @@ WNDPROC cbOldResultTabFilterEdit; LRESULT CALLBACK cbNewEditor(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewTree(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewTreeItemEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +LRESULT CALLBACK cbNewTreeSearch(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewSchema(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewEditorSearch(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK cbNewEditorSearchString(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); @@ -248,6 +249,12 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin hResultMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDC_MENU_RESULT)); hResultMenu = GetSubMenu(hResultMenu, 0); + int menuPos = Menu_GetItemPositionByName(hResultMenu, TEXT("Column modifier")); + if (menuPos != -1) { + hModifierMenu = GetSubMenu(hResultMenu, menuPos); + } else { + MessageBox(hMainWnd, TEXT("Result menu bug"), 0, MB_OK); + } hTabResultMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDC_MENU_TAB_RESULT)); hTabResultMenu = GetSubMenu(hTabResultMenu, 0); @@ -396,6 +403,11 @@ int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLin hTreeWnd = CreateWindowEx(0, WC_TREEVIEW, NULL, WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | WS_DISABLED | TVS_EDITLABELS, 0, 0, 100, 100, hMainWnd, (HMENU)IDC_TREE, hInstance, NULL); SetProp(hTreeWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hTreeWnd, GWLP_WNDPROC, (LONG_PTR)&cbNewTree)); DragAcceptFiles(hTreeWnd, TRUE); + hTreeSearchWnd = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("RICHEDIT50W"), NULL, WS_CHILD, 0, 0, 100, 100, hMainWnd, (HMENU)IDC_TREE_SEARCH, hInstance, NULL); + SendMessage(hTreeSearchWnd, WM_SETFONT, (LPARAM)hFont, 0); + SetProp(hTreeSearchWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hTreeSearchWnd, GWLP_WNDPROC, (LONG_PTR)&cbNewTreeSearch)); + SendMessage(hTreeSearchWnd, EM_SETEVENTMASK, 0, ENM_CHANGE); + hMainTabWnd = CreateWindowEx(0, WC_STATIC, NULL, WS_VISIBLE | WS_CHILD | SS_NOTIFY, 100, 0, 100, 100, hMainWnd, (HMENU)IDC_MAINTAB, hInstance, NULL); createTooltip(hMainWnd); @@ -976,6 +988,14 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam rc = (RECT){spX, spY, w, spY + 10}; FillRect((HDC)wParam, &rc, hBrush); + if (IsWindowVisible(hTreeSearchWnd)) { + RECT rcSearch; + GetWindowRect(hTreeSearchWnd, &rcSearch); + InflateRect(&rcSearch, 5, 5); + MapWindowPoints(HWND_DESKTOP, hWnd, (LPPOINT) &rcSearch, 2); + FillRect((HDC)wParam, &rcSearch, hBrush); + } + DeleteObject(hBrush); return 1; @@ -1270,7 +1290,7 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam _tcscat(tmpPath16, TEXT("sqlite-gui\\update\\*.*\0")); TCHAR pluginPath16[MAX_PATH + 1] = {0}; - _sntprintf(pluginPath16, MAX_PATH, TEXT("%ls" PLUGIN_DIRECTORY "%ls\\"), APP_PATH, ADDON_TYPES16[ADDON_VALUE_VIEWER]); + _sntprintf(pluginPath16, MAX_PATH, TEXT("%ls" PLUGIN_DIRECTORY "%ls\\"), APP_PATH, ADDON_EXTS16[ADDON_VALUE_VIEWER]); SHFILEOPSTRUCT fo = {0}; fo.wFunc = FO_MOVE; @@ -1279,7 +1299,31 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam fo.pTo = pluginPath16; SHFileOperation(&fo); - reloadPlugins(); + reloadPlugins(ADDON_VALUE_VIEWER); + } + } + + if (cmd == IDM_MODIFIER_PLUGINS) { + if (DLG_OK == DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_ADDON_MANAGER), hMainWnd, (DLGPROC)dialogs::cbDlgAddonManager, (LPARAM)ADDON_COLUMN_MODIFIER)) { + + // Reset all plugins + EnumChildWindows(hMainWnd, (WNDENUMPROC)cbEnumChildren, (LPARAM)ACTION_RESET_MODIFIERS); + + TCHAR tmpPath16[MAX_PATH + 1] = {0}; + GetTempPath(MAX_PATH, tmpPath16); + _tcscat(tmpPath16, TEXT("sqlite-gui\\update\\*.*\0")); + + TCHAR pluginPath16[MAX_PATH + 1] = {0}; + _sntprintf(pluginPath16, MAX_PATH, TEXT("%ls" PLUGIN_DIRECTORY "%ls\\"), APP_PATH, ADDON_EXTS16[ADDON_COLUMN_MODIFIER]); + + SHFILEOPSTRUCT fo = {0}; + fo.wFunc = FO_MOVE; + fo.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOCONFIRMMKDIR | FOF_NOERRORUI | FOF_FILESONLY; + fo.pFrom = tmpPath16; + fo.pTo = pluginPath16; + SHFileOperation(&fo); + + reloadPlugins(ADDON_COLUMN_MODIFIER); } } @@ -1292,6 +1336,7 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam updateCustomFunction(false); reloadMeta(FUNCTIONS, "select distinct name from pragma_function_list()"); processHighlight(hEditorWnd, true, true, true); + reloadPlugins(ADDON_COLUMN_MODIFIER); } if (cmd == IDM_ENCRYPTION) { @@ -1588,8 +1633,10 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (!SendMessage(hToolbarWnd, TB_ISBUTTONHIDDEN, IDM_INTERRUPT, 0)) return SendMessage(hWnd, WM_COMMAND, IDM_INTERRUPT, 0); - if (GetDlgCtrlID(GetFocus()) == IDC_EDITOR_SEARCH_STRING) - return SendMessage(GetFocus(), WM_KEYDOWN, VK_ESCAPE, 0); + HWND hFocusWnd = GetFocus(); + int focusId = GetDlgCtrlID(hFocusWnd); + if (focusId == IDC_EDITOR_SEARCH_STRING || focusId == IDC_TREE_SEARCH) + return SendMessage(hFocusWnd, WM_KEYDOWN, VK_ESCAPE, 0); int exitByEscape = prefs::get("exit-by-escape"); if((exitByEscape == 1 || (exitByEscape == 2 && MessageBox(hMainWnd, TEXT("Are you sure you want to close the app?"), TEXT("Exit confirmation"), MB_YESNO) == IDYES)) && @@ -1668,12 +1715,13 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (cmd == IDM_ADD && type == TABLE) { _sntprintf(name16, 255, schema16); if (DLG_OK == DialogBoxParam (GetModuleHandle(0), MAKEINTRESOURCE(IDD_ADD_TABLE), hMainWnd, (DLGPROC)&dialogs::cbDlgAddTable, (LPARAM)name16)) { + updateTables(schema16); updateReferences(); updateTree(TABLE, name16); } } - if ((cmd == IDM_ADD && type != TABLE) || cmd == IDM_VIEW || cmd == IDM_EDIT) { + if ((cmd == IDM_ADD && type != TABLE) || ((cmd == IDM_VIEW || cmd == IDM_EDIT) && type != COLUMN)) { int len = _tcslen(fullname16) + 2; TCHAR buf16[len + 1]; _sntprintf(buf16, len, TEXT(" %ls"), fullname16); @@ -2062,9 +2110,34 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam SendMessage(hMainWnd, WMU_UPDATE_SIZES, 0, 0); } - if (cmd == IDM_RESULT_HEATMAP) { + if (cmd >= IDM_RESULT_MODIFIER && cmd <= IDM_RESULT_MODIFIER + 100) { HWND hListWnd = (HWND)SendMessage(hWnd, WMU_GET_CURRENT_RESULTSET, 0, 0); - SendMessage(hListWnd, WMU_HEATMAP, 0, 0); + int colNo = currCell.iSubItem; + + int* modifiers = (int*)GetProp(hListWnd, TEXT("MODIFIERS")); + int* modifierRows = (int*)GetProp(hListWnd, TEXT("MODIFIER_ROWS")); + if (!modifiers || !modifierRows) { + MessageBox(hWnd, TEXT("Can't set modifier for the column: no modifiers"), TEXT("Internal error"), MB_OK); + return false; + } + + SendMessage(hListWnd, WMU_RESET_MODIFIER, colNo, 0); + + bool rc = false; + TCHAR err16[MAX_MODIFIER_ERROR_LEN + 1] {0}; + int modifierNo = Menu_GetItemData(hModifierMenu, cmd); + if ((modifierNo > 0 && modifierNo < MAX_PLUGIN_COUNT && plugins[modifierNo].hModule) || modifierNo <= 0) { + rc = true; + modifierRows[colNo] = currCell.iItem; + if (modifierNo > 0 && plugins[modifierNo].activate) + rc = plugins[modifierNo].activate(hListWnd, colNo, err16); + + InvalidateRect(hListWnd, NULL, TRUE); + } + + modifiers[colNo] = rc ? modifierNo : 0; + if (!rc && _tcslen(err16)) + MessageBox(hWnd, err16, plugins[modifierNo].name, MB_OK); } if (cmd == IDM_RESULT_CHART || cmd == IDM_RESULT_VALUE_FILTER || cmd == IDM_RESULT_COPY_CELL || cmd == IDM_RESULT_COPY_ROW || cmd == IDM_RESULT_EXPORT) @@ -2116,13 +2189,10 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam int currResultNo = GetWindowLongPtr(hCurrListWnd, GWLP_USERDATA); HMENU hCompareMenu = GetSubMenu(hResultMenu, GetMenuItemCount(hResultMenu) - 1); - MENUITEMINFO mii{0}; - mii.cbSize = sizeof(MENUITEMINFO); - mii.fMask = MIIM_DATA; - GetMenuItemInfo(hCompareMenu, cmd, false, &mii); - int tabNo = LOWORD(mii.dwItemData); - int resultNo = HIWORD(mii.dwItemData); + ULONG_PTR data = Menu_GetItemData(hCompareMenu, cmd); + int tabNo = LOWORD(data); + int resultNo = HIWORD(data); TCHAR title[1024]{0}; TCHAR buf[256]; @@ -2465,6 +2535,9 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (LOWORD(wParam) == IDC_EDITOR && HIWORD(wParam) == EN_CHANGE) SendMessage((HWND)lParam, WMU_TEXT_CHANGED, 0, 0); + if (LOWORD(wParam) == IDC_TREE_SEARCH && HIWORD(wParam) == EN_CHANGE) + updateTree(0); + if (LOWORD(wParam) == IDC_EDITOR && HIWORD(wParam) == EN_KILLFOCUS && IsWindowVisible(hTooltipWnd)) hideTooltip(); @@ -2545,24 +2618,9 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (rowNo == -1 || colNo < 1 || ListView_GetSelectedCount(hListWnd) > 1) return 0; - TCHAR*** cache = (TCHAR***)GetProp(hListWnd, TEXT("CACHE")); - int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); - byte* datatypes = (byte*)GetProp(hListWnd, TEXT("DATATYPES")); - unsigned char** blobs = (unsigned char**)GetProp(hListWnd, TEXT("BLOBS")); - - int colCount = ListView_GetColumnCount(hListWnd); - int no = colNo + resultset[rowNo] * (colCount - 1); - TCHAR* text16 = cache[resultset[rowNo]][colNo]; - - if (datatypes[no] == SQLITE_NULL) - return 0; - - TDlgValueParam data = {0}; - if (datatypes[no] == SQLITE_BLOB) - data = {SQLITE_BLOB, blobs[no]}; - else - data = {SQLITE_TEXT, (const unsigned char*)text16}; - openDialog(IDD_VALUE_VIEWER, (DLGPROC)dialogs::cbDlgValueViewer, (LPARAM)&data); + TValue value = {0}; + if (ListView_GetItemValue(hListWnd, rowNo, colNo, &value)) + openDialog(IDD_VALUE_VIEWER, (DLGPROC)dialogs::cbDlgValueViewer, (LPARAM)&value); } @@ -2697,6 +2755,9 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (pHdr->hwndFrom == hTreeWnd && pHdr->code == (DWORD)TVN_KEYDOWN) { NMTVKEYDOWN* pKd = (LPNMTVKEYDOWN) lParam; + bool isCtrl = HIWORD(GetKeyState(VK_CONTROL)); + bool isShift = HIWORD(GetKeyState(VK_SHIFT)); + if ((pKd->wVKey == VK_DELETE)) { TVITEM ti{0}; ti.hItem = treeItems[0]; @@ -2706,7 +2767,7 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam PostMessage(hMainWnd, WM_COMMAND, IDM_DELETE, 0); } - if (pKd->wVKey == 0x43 && GetKeyState(VK_CONTROL)) {// Ctrl + C + if (isCtrl && pKd->wVKey == 0x43) {// Ctrl + C TCHAR name16[256] = {0}; TV_ITEM tv; tv.mask = TVIF_TEXT | TVIF_PARAM; @@ -2720,7 +2781,7 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (tv.lParam == COLUMN) _tcstok(name16, TEXT(":")); - if (tv.lParam == TABLE && HIWORD(GetKeyState(VK_SHIFT))) { + if (isShift && tv.lParam == TABLE) { TCHAR* ddl = getDDL(TEXT("main"), name16, tv.lParam, false); if (ddl != NULL) { utils::setClipboardText(ddl); @@ -2731,6 +2792,12 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam } } + if (isCtrl && pKd->wVKey == 0x46) {// Ctrl + F + ShowWindow(hTreeSearchWnd, SW_SHOW); + SetFocus(hTreeSearchWnd); + SendMessage(hMainWnd, WMU_UPDATE_SIZES, 0, 0); + } + return 1; } @@ -2742,7 +2809,11 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (pHdr->hwndFrom == hTreeWnd && pHdr->code == TVN_BEGINLABELEDIT) { const NMTVDISPINFO * pMi = (LPNMTVDISPINFO)lParam; - if (!TreeView_GetParent(hTreeWnd, pMi->item.hItem)) + HTREEITEM hParentItem = TreeView_GetParent(hTreeWnd, pMi->item.hItem); + if (!hParentItem) + return true; + + if (TreeView_GetItemParam(hTreeWnd, hParentItem) == VIEW) return true; if (pMi->item.state & TVIS_CUT) @@ -2867,8 +2938,10 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (pCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) { UINT state = TreeView_GetItemState(pHdr->hwndFrom, (HTREEITEM)pCustomDraw->nmcd.dwItemSpec, TVIS_CUT); + UINT type = TreeView_GetItemParam(pHdr->hwndFrom, (HTREEITEM)pCustomDraw->nmcd.dwItemSpec); pCustomDraw->clrText = pCustomDraw->clrTextBk != RGB(255, 255, 255) ? RGB(255, 255, 255) : state & TVIS_CUT ? RGB(200, 200, 200) : + state & TVIS_BOLD && type == COLUMN ? RGB(200, 0, 200) : state & TVIS_BOLD ? RGB(0, 0, 200) : RGB(0, 0, 0); } @@ -2982,7 +3055,19 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam bool isTransposed = GetProp(hListWnd, TEXT("ISTRANSPOSED")); UINT tState = (isTransposed ? MF_CHECKED : MF_UNCHECKED) | (!isTransposed && *(int*)GetProp(hListWnd, TEXT("TOTALROWCOUNT")) > MAX_TRANSPOSE_ROWS ? MF_DISABLED | MF_GRAYED : 0); Menu_SetItemState(hResultMenu, IDM_RESULT_TRANSPOSE, tState); - Menu_SetItemState(hResultMenu, IDM_RESULT_HEATMAP, GetProp(hListWnd, TEXT("HEATMAP")) ? MF_CHECKED : MF_UNCHECKED); + + int* modifiers = (int*)GetProp(hListWnd, TEXT("MODIFIERS")); + if (modifiers) { + int itemCount = GetMenuItemCount(hModifierMenu); + for (int itemNo = 0; itemNo < itemCount; itemNo++) { + int id = GetMenuItemID(hModifierMenu, itemNo); + int modifierNo = Menu_GetItemData(hModifierMenu, id); + Menu_SetItemState(hModifierMenu, id, modifierNo == modifiers[currCell.iSubItem] ? MF_CHECKED : MF_UNCHECKED); + } + + if (itemCount == 1) + Menu_SetItemState(hModifierMenu, IDM_RESULT_MODIFIER, MF_UNCHECKED | MF_DISABLED | MF_GRAYED); + } TrackPopupMenu(hResultMenu, TPM_RIGHTBUTTON | TPM_TOPALIGN | TPM_LEFTALIGN, p.x, p.y, 0, hMainWnd, NULL); } @@ -3252,13 +3337,15 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam int splitterY = CLAMP(prefs::get("splitter-position-y"), 50 * z + rcToolbar.bottom, h - 50 * z); int tabH = 19 * z; int editorSearchH = IsWindowVisible(hEditorSearchWnd) ? rcEditorSearch.bottom : 0; + int treeSearchH = IsWindowVisible(hTreeSearchWnd) ? utils::getEditHeightByFont(hTreeSearchWnd) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME): 0; RECT rcEditor; GetClientRect(hEditorWnd, &rcEditor); LONG flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_DEFERERASE; SetWindowPos(hSchemaWnd, 0, 0, top + 2, splitterX, tabH, flags); - SetWindowPos(hTreeWnd, 0, 0, top + tabH + 2, splitterX, h - tabH - 2, flags); + SetWindowPos(hTreeWnd, 0, 0, top + tabH + 2, splitterX, h - tabH - 2 - treeSearchH - (treeSearchH > 0), flags); + SetWindowPos(hTreeSearchWnd, 0, 0, top + h - treeSearchH, splitterX, treeSearchH, flags); SetWindowPos(hMainTabWnd, 0, splitterX + 5, top + 2, rc.right - splitterX - 5, tabH, flags); SetWindowPos(hEditorWnd, 0, splitterX + 5, top + tabH + 2, rc.right - splitterX - 5, splitterY - tabH + 2 - editorSearchH, flags); SetWindowPos(hEditorSearchWnd, 0, splitterX + 5, top + splitterY - rcEditorSearch.bottom + 2, rc.right - splitterX - 5, rcEditorSearch.bottom, flags); @@ -3268,6 +3355,7 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam rcEditor = (RECT){0, rcEditor.bottom - dy, rcEditor.right, rcEditor.bottom + dy}; InvalidateRect(hEditorWnd, &rcEditor, FALSE); InvalidateRect(hSchemaWnd, NULL, TRUE); + InvalidateRect(hTreeSearchWnd, NULL, TRUE); SetWindowPos(hTabWnd, 0, splitterX + 3, top + splitterY + 5, rc.right - splitterX, h - splitterY - 3, flags); EnumChildWindows(hTabWnd, (WNDENUMPROC)cbEnumChildren, ACTION_RESIZETAB); @@ -3281,6 +3369,7 @@ LRESULT CALLBACK cbMainWindow (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam setEditorFont(hEditorWnd); SendMessage(hTreeWnd, WM_SETFONT, (LPARAM)hFont, FALSE); + SendMessage(hTreeSearchWnd, WM_SETFONT, (LPARAM)hFont, FALSE); SendMessage(hAutoComplete, WM_SETFONT, (LPARAM)hFont, FALSE); SendMessage(hAutoCompleteHelp, WM_SETFONT, (LPARAM)hFont, FALSE); SendMessage(hTooltipWnd, WM_SETFONT, (LPARAM)hFont, FALSE); @@ -3361,6 +3450,7 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam break; case WM_DESTROY : { + SendMessage(hWnd, WMU_RESET_MODIFIER, -1, 0); SendMessage(hWnd, WMU_RESET_CACHE, 0, 0); int* pOrderBy = (int*)GetProp(hWnd, TEXT("ORDERBY")); @@ -3466,86 +3556,6 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam } break; - case WMU_HEATMAP: { - bool isVirtual = GetWindowLong(hWnd, GWL_STYLE) & LVS_OWNERDATA; - if (!isVirtual) - return 0; - - COLORREF* heatmap = (COLORREF*)GetProp(hWnd, TEXT("HEATMAP")); - bool isHeatmap = heatmap != NULL; - bool isCtrl = HIWORD(GetKeyState(VK_CONTROL)); - bool isShift = HIWORD(GetKeyState(VK_SHIFT)); - if (heatmap) { - delete [] heatmap; - RemoveProp(hWnd, TEXT("HEATMAP")); - } - - if (isHeatmap && !isCtrl && !isShift) { - InvalidateRect(hWnd, NULL, TRUE); - return 0; - } - - HWND hHeader = ListView_GetHeader(hWnd); - int colCount = Header_GetItemCount(hHeader) - 1; - int rowCount = *(int*)GetProp(hWnd, TEXT("TOTALROWCOUNT")); - if (colCount == 0 || rowCount == 0) - return 0; - - TCHAR*** cache = (TCHAR***)GetProp(hWnd, TEXT("CACHE")); - - auto getColor = [](double min, double max, double value, bool isReverse) { - double v = 2 * (value - min)/(max - min); // --> (0, 2) - return isReverse ? - (v < 1 ? RGB(255, v * 255, 0) : RGB((2 - v) * 255, 255, 0)) : - (v < 1 ? RGB(v * 255, 255, 0) : RGB(255, (2 - v) * 255, 0)); - }; - - heatmap = new COLORREF[rowCount * (colCount + 1)] {0}; - - double NO_VALUE = 0.00012003; - bool isSharedExtremes = isCtrl; - bool isReverseColors = HIWORD(GetKeyState(VK_SHIFT)); - if (isSharedExtremes) { - double min = NO_VALUE, max = NO_VALUE; - for (int colNo = 1; colNo <= colCount; colNo++) { - for (int rowNo = 0; rowNo < rowCount; rowNo++) { - double value; - if (utils::isNumber(cache[rowNo][colNo], &value)) { - min = min == NO_VALUE || min > value ? value : min; - max = max == NO_VALUE || max < value ? value : max; - } - } - } - - for (int colNo = 1; colNo <= colCount; colNo++) { - for (int rowNo = 0; rowNo < rowCount; rowNo++) { - double value; - heatmap[colNo + rowNo * colCount] = min != NO_VALUE && utils::isNumber(cache[rowNo][colNo], &value) ? getColor(min, max, value, isReverseColors) : RGB(255, 255, 255); - } - } - } else { - for (int colNo = 1; colNo <= colCount; colNo++) { - double min = NO_VALUE, max = NO_VALUE; - for (int rowNo = 0; rowNo < rowCount; rowNo++) { - double value; - if (utils::isNumber(cache[rowNo][colNo], &value)) { - min = min == NO_VALUE || min > value ? value : min; - max = max == NO_VALUE || max < value ? value : max; - } - } - - for (int rowNo = 0; rowNo < rowCount; rowNo++) { - double value; - heatmap[colNo + rowNo * colCount] = min != NO_VALUE && utils::isNumber(cache[rowNo][colNo], &value) ? getColor(min, max, value, isReverseColors) : RGB(255, 255, 255); - } - } - } - SetProp(hWnd, TEXT("HEATMAP"), heatmap); - - InvalidateRect(hWnd, NULL, TRUE); - } - break; - case WMU_UPDATE_RESULTSET: { bool isVirtual = GetWindowLong(hWnd, GWL_STYLE) & LVS_OWNERDATA; if (!isVirtual) @@ -3560,8 +3570,6 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam int* pRowCount = (int*)GetProp(hWnd, TEXT("ROWCOUNT")); int* pOrderBy = (int*)GetProp(hWnd, TEXT("ORDERBY")); int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET")); - if (resultset) - free(resultset); if (!cache || *pTotalRowCount == 0) { SendMessage(hWnd, WMU_SET_HEADER_FILTERS, 0, 0); @@ -3572,6 +3580,9 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (colCount == 0) return 1; + if (resultset) + free(resultset); + BOOL* bResultset = (BOOL*)calloc(*pTotalRowCount, sizeof(BOOL)); for (int rowNo = 0; rowNo < *pTotalRowCount; rowNo++) bResultset[rowNo] = TRUE; @@ -3588,7 +3599,7 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam if (!bResultset[rowNo]) continue; - TCHAR* value = cache[rowNo][colNo]; + TCHAR* value = ListView_GetItemValueText(hListWnd, rowNo, colNo, true); //cache[rowNo][colNo]; if (len > 1 && (filter[0] == TEXT('<') || filter[0] == TEXT('>')) && utils::isNumber(filter + 1, NULL)) { TCHAR* end = 0; double df = _tcstod(filter + 1, &end); @@ -3662,7 +3673,7 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam case WMU_SET_HEADER_FILTERS: { HWND hHeader = ListView_GetHeader(hWnd); - int isShowFilters = prefs::get("show-filters"); + int isShowFilters = prefs::get("show-filters") || GetDlgCtrlID(hWnd) == IDC_DLG_ROWS; int colCount = Header_GetItemCount(hHeader); SendMessage(hWnd, WM_SETREDRAW, FALSE, 0); @@ -3814,26 +3825,38 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam int* resultset = (int*)GetProp(hWnd, TEXT("RESULTSET")); if (resultset) { - SetProp(hWnd, TEXT("RESULTSET"), (HANDLE)0); + SetProp(hWnd, TEXT("RESULTSET"), 0); free(resultset); } byte* datatypes = (byte*)GetProp(hWnd, TEXT("DATATYPES")); if (datatypes) { - SetProp(hWnd, TEXT("DATATYPES"), (HANDLE)0); + SetProp(hWnd, TEXT("DATATYPES"), 0); free(datatypes); } - COLORREF* heatmap = (COLORREF*)GetProp(hWnd, TEXT("HEATMAP")); - if (heatmap) { - SetProp(hWnd, TEXT("HEATMAP"), (HANDLE)0); - delete [] heatmap; + int* modifiers = (int*)GetProp(hWnd, TEXT("MODIFIERS")); + if (modifiers) { + SetProp(hWnd, TEXT("MODIFIERS"), 0); + free(modifiers); + } + + int* modifierRows = (int*)GetProp(hWnd, TEXT("MODIFIER_ROWS")); + if (modifierRows) { + SetProp(hWnd, TEXT("MODIFIER_ROWS"), 0); + free(modifierRows); + } + + TCHAR* modifierBuffer = (TCHAR*)GetProp(hWnd, TEXT("MODIFIERBUFFER")); + if (modifierBuffer) { + SetProp(hWnd, TEXT("MODIFIERBUFFER"), 0); + delete [] modifierBuffer; } int vCount = (int)(LONG_PTR)GetProp(hWnd, TEXT("VALUECOUNT")); unsigned char** blobs = (unsigned char**)GetProp(hWnd, TEXT("BLOBS")); if (blobs) { - SetProp(hWnd, TEXT("BLOBS"), (HANDLE)0); + SetProp(hWnd, TEXT("BLOBS"), 0); for (int i = 0; i < vCount; i++) if (blobs[i]) @@ -3842,6 +3865,26 @@ LRESULT CALLBACK cbNewListView(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam } } break; + + // wParam = colNo, -1 = all + case WMU_RESET_MODIFIER: { + int* modifiers = (int*)GetProp(hWnd, TEXT("MODIFIERS")); + if (modifiers) { + int _colNo = (int)wParam; + int colCount = ListView_GetColumnCount(hWnd); + for (int colNo = 0; colNo < colCount; colNo++) { + if (_colNo != colNo && _colNo != -1) + continue; + + int modifierNo = modifiers[colNo]; + if (modifierNo > 0 && modifierNo < MAX_PLUGIN_COUNT && plugins[modifierNo].hModule && plugins[modifierNo].deactivate) + plugins[modifierNo].deactivate(hWnd, colNo); + modifiers[colNo] = 0; + } + InvalidateRect(hWnd, NULL, TRUE); + } + } + break; } return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam); @@ -4077,6 +4120,21 @@ LRESULT CALLBACK cbNewTreeItemEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lP return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam); } +LRESULT CALLBACK cbNewTreeSearch(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { + if (msg == WM_CHAR && wParam == VK_ESCAPE) + return 0; + + if (msg == WM_KEYDOWN && wParam == VK_ESCAPE) { + SetFocus(hTreeWnd); + SetWindowText(hWnd, NULL); + ShowWindow(hWnd, SW_HIDE); + SendMessage(hMainWnd, WMU_UPDATE_SIZES, 0, 0); + return 0; + } + + return CallWindowProc((WNDPROC)GetProp(hWnd, TEXT("WNDPROC")), hWnd, msg, wParam, lParam); +} + LRESULT CALLBACK cbNewResultTabFilterEdit(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg){ case WM_PAINT: { @@ -5236,17 +5294,21 @@ void updateCustomFunction(bool unreg, sqlite3* conn) { HWND hCodesWnd = GetDlgItem(hMainWnd, IDC_FUNCTION_CODES); auto update = [hCodesWnd](bool unreg, sqlite3* conn, const char* name8, const char* code8) { + sqlite3_stmt* stmt; + int nargs = SQLITE_OK == sqlite3_prepare_v2(db, code8, -1, &stmt, 0) ? sqlite3_bind_parameter_count(stmt) : -1; + sqlite3_finalize(stmt); + if (unreg) { char* stored8 = (char*)GetPropA(hCodesWnd, name8); if (stored8) free(stored8); - sqlite3_create_function(conn, name8, -1, SQLITE_UTF8, 0, 0, 0, 0); + sqlite3_create_function(conn, name8, nargs, SQLITE_UTF8, 0, 0, 0, 0); } else { char* stored8 = (char*)GetPropA(hCodesWnd, name8); if (!stored8) stored8 = strdup(code8); - sqlite3_create_function(conn, name8, -1, SQLITE_UTF8, (void*)stored8, dbutils::userDefinedFunction, 0, 0); + sqlite3_create_function(conn, name8, nargs, SQLITE_UTF8, (void*)stored8, dbutils::userDefinedFunction, 0, 0); } }; @@ -5850,80 +5912,157 @@ int pluginSorter (const void* a, const void* b) { return (*(TPlugin*)b).priority - (*(TPlugin*)a).priority; } -void reloadPlugins() { - for (int i = 0; i < MAX_PLUGIN_COUNT; i++) { - if (plugins[i].hModule) { +// 0-index as None-plugin +void reloadPlugins(int type) { + for (int i = 1; i < MAX_PLUGIN_COUNT; i++) { + if (plugins[i].hModule && (type == 0 || plugins[i].type == type)) { FreeLibrary(plugins[i].hModule); plugins[i] = {0}; } } - int pluginNo = 0; + auto getNextPluginNo = [] () { + int res = 1; + while (res <= MAX_PLUGIN_COUNT && plugins[res].hModule) + res++; + return res; + }; + + int nextPluginNo = getNextPluginNo(); TCHAR pluginsDir16[MAX_PATH + 1]{0}; _sntprintf(pluginsDir16, MAX_PATH, TEXT("%ls" PLUGIN_DIRECTORY), APP_PATH); - for (int pluginTypeNo = 1; pluginTypeNo < 3; pluginTypeNo++) { - const TCHAR* pluginType16 = ADDON_TYPES16[pluginTypeNo]; + for (int pluginType = 1; pluginType < 3; pluginType++) { + if (type && pluginType != type) + continue; + + const TCHAR* pluginExt16 = ADDON_EXTS16[pluginType]; TCHAR searchPath16[MAX_PATH]; - _sntprintf(searchPath16, MAX_PATH, TEXT("%ls%ls\\*.*"), pluginsDir16, pluginType16); + _sntprintf(searchPath16, MAX_PATH, TEXT("%ls%ls\\*.*"), pluginsDir16, pluginExt16); WIN32_FIND_DATA ffd; HANDLE hFind = FindFirstFile(searchPath16, &ffd); if (hFind == INVALID_HANDLE_VALUE) - break; + continue; sqlite3_stmt* stmt; if (SQLITE_OK != sqlite3_prepare_v2(prefs::db, "select 1 from main.addons where name = ?1 and type = ?2 and enable = 0", -1, &stmt, 0)) { sqlite3_finalize(stmt); - break; + continue; } do { TCHAR filepath16[MAX_PATH + 1]{0}; - _sntprintf(filepath16, MAX_PATH, TEXT("%ls%ls\\%ls"), pluginsDir16, pluginType16, ffd.cFileName); + _sntprintf(filepath16, MAX_PATH, TEXT("%ls%ls\\%ls"), pluginsDir16, pluginExt16, ffd.cFileName); TCHAR ext16[32]{0}; TCHAR name16[32]{0}; _tsplitpath(filepath16, NULL, NULL, name16, ext16); - if (_tcscmp(ext16 + 1 /* skip first dot .ext */, pluginType16)) + if (_tcscmp(ext16 + 1 /* skip first dot .ext */, pluginExt16)) continue; char* name8 = utils::utf16to8(name16); sqlite3_reset(stmt); sqlite3_bind_text(stmt, 1, name8, strlen(name8), SQLITE_TRANSIENT); - sqlite3_bind_int(stmt, 2, pluginTypeNo); + sqlite3_bind_int(stmt, 2, pluginType); bool isEnable = SQLITE_ROW != sqlite3_step(stmt); delete [] name8; - if (isEnable) { + if (isEnable && nextPluginNo < MAX_PLUGIN_COUNT) { HMODULE hModule = LoadLibrary(filepath16); if (hModule) { + pluginView view = (pluginView)GetProcAddress(hModule, "view"); pluginClose close = (pluginClose)GetProcAddress(hModule, "close"); pluginGetPriority getPriority = (pluginGetPriority)GetProcAddress(hModule, "getPriority"); - if (view && close) { - plugins[pluginNo] = { + + pluginActivate activate = (pluginActivate)GetProcAddress(hModule, "activate"); + pluginDeactivate deactivate = (pluginDeactivate)GetProcAddress(hModule, "deactivate"); + pluginSetText setText = (pluginSetText)GetProcAddress(hModule, "setText"); + pluginSetColor setColor = (pluginSetColor)GetProcAddress(hModule, "setColor"); + pluginRender render = (pluginRender)GetProcAddress(hModule, "render"); + + if ((pluginType == ADDON_VALUE_VIEWER && view && close) || + (pluginType == ADDON_COLUMN_MODIFIER && (setText || render || setColor))) { + plugins[nextPluginNo] = { hModule, view, close, - pluginTypeNo, + activate, + deactivate, + setText, + setColor, + render, + pluginType, getPriority != NULL ? getPriority() : 0 }; - pluginNo++; + _tsplitpath(filepath16, NULL, NULL, plugins[nextPluginNo].name, NULL); + + nextPluginNo = getNextPluginNo(); } else { FreeLibrary(hModule); } } } - } while (FindNextFile(hFind, &ffd) && pluginNo < MAX_PLUGIN_COUNT); + } while (FindNextFile(hFind, &ffd) && nextPluginNo < MAX_PLUGIN_COUNT); FindClose(hFind); sqlite3_finalize(stmt); } qsort(plugins, MAX_PLUGIN_COUNT, sizeof(TPlugin), pluginSorter); + + if (type == 0 || type == ADDON_COLUMN_MODIFIER) { + int cnt = GetMenuItemCount(hModifierMenu); + for (int itemNo = 0; itemNo < cnt; itemNo++) + DeleteMenu(hModifierMenu, 0, MF_BYPOSITION); + + Menu_InsertItem(hModifierMenu, 0, IDM_RESULT_MODIFIER + 0, MF_STRING, TEXT("None")); + + int itemNo = 1; + bool isSeparator = false; + for (int pluginNo = 1; pluginNo < MAX_PLUGIN_COUNT; pluginNo++) { + if (plugins[pluginNo].hModule && plugins[pluginNo].type == ADDON_COLUMN_MODIFIER) { + if (!isSeparator) { + AppendMenu(hModifierMenu, MF_STRING, 0, 0); + isSeparator = true; + } + AppendMenu(hModifierMenu, MF_STRING, IDM_RESULT_MODIFIER + itemNo, plugins[pluginNo].name); + Menu_SetItemData(hModifierMenu, IDM_RESULT_MODIFIER + itemNo, pluginNo); + itemNo++; + } + } + + isSeparator = false; + sqlite3_stmt* stmt; + + if (SQLITE_OK == sqlite3_prepare_v2(prefs::db, "select id, name, code from functions f order by name", -1, &stmt, 0)) { + while (SQLITE_ROW == sqlite3_step(stmt)) { + sqlite3_stmt* stmt2; + int nargs = SQLITE_OK == sqlite3_prepare_v2(prefs::db, (const char*)sqlite3_column_text(stmt, 2), -1, &stmt2, 0) ? sqlite3_bind_parameter_count(stmt2) : -1; + sqlite3_finalize(stmt2); + + if (nargs != 1) + continue; + + if (!isSeparator) { + AppendMenu(hModifierMenu, MF_STRING, 0, 0); + isSeparator = true; + } + TCHAR* name16 = utils::utf8to16((const char*)sqlite3_column_text(stmt, 1)); + AppendMenu(hModifierMenu, MF_STRING, IDM_RESULT_MODIFIER + itemNo, name16); + Menu_SetItemData(hModifierMenu, IDM_RESULT_MODIFIER + itemNo, -sqlite3_column_int(stmt, 0)); + delete [] name16; + itemNo++; + } + } + sqlite3_finalize(stmt); + + if (itemNo == 1) + Menu_SetItemText(hModifierMenu, IDM_RESULT_MODIFIER + 0, TEXT("No plugin installed")); + } } int Toolbar_SetButtonState(HWND hToolbar, int id, byte state, LPARAM lParam) { @@ -5995,10 +6134,7 @@ bool CALLBACK cbEnumChildren (HWND hWnd, LPARAM action) { int id = (int)(GetDlgCtrlID(hWnd) / 100) * 100; if (id == IDC_TAB_ROWS) { ShowWindow(hWnd, SW_SHOW); - // ??? - // SetFocus(hWnd); - // int pos = ListView_GetNextItem(hWnd, -1, LVNI_SELECTED); - // ListView_SetItemState (hWnd, pos == -1 ? 0 : pos, LVIS_FOCUSED | LVIS_SELECTED, 0x000F); + int tabNo = SendMessage(hMainTabWnd, WMU_TAB_GET_CURRENT, 0, 0); SendMessage(hStatusWnd, SB_SETTEXT, SB_ELAPSED_TIME, (LPARAM)tabs[tabNo].queryElapsedTimes[resultNo]); SendMessage(hMainWnd, WMU_UPDATE_SB_RESULTSET, 0, 0); @@ -6031,6 +6167,10 @@ bool CALLBACK cbEnumChildren (HWND hWnd, LPARAM action) { } } + if (action == ACTION_RESET_MODIFIERS && (int)(GetDlgCtrlID(hWnd)) == IDC_TAB_ROWS && GetWindowLong(hWnd, GWL_STYLE) & LVS_OWNERDATA) { + SendMessage(hWnd, WMU_RESET_MODIFIER, -1, 0); + } + return true; } @@ -6080,7 +6220,6 @@ COLORREF RichEdit_GetTextColor (HWND hWnd, int pos) { HTREEITEM TreeView_AddItem (const TCHAR* caption, HTREEITEM parent = TVI_ROOT, int type = 0, int disabled = 0, int pinned = 0) { TVITEM tvi{0}; - TVINSERTSTRUCT tvins{0}; tvi.mask = TVIF_TEXT | TVIF_PARAM | TVIF_STATE; tvi.pszText = (TCHAR*)caption; tvi.cchTextMax = _tcslen(caption) + 1; @@ -6096,12 +6235,22 @@ HTREEITEM TreeView_AddItem (const TCHAR* caption, HTREEITEM parent = TVI_ROOT, i tvi.state = TVIS_CUT; } + TVINSERTSTRUCT tvins{0}; tvins.item = tvi; tvins.hInsertAfter = pinned ? (HTREEITEM)GetProp(hTreeWnd, TEXT("LASTPINNED")) : parent != TVI_ROOT || type == 0 ? TVI_LAST : abs(type) == 1 ? TVI_FIRST : treeItems[abs(type) - 1]; //pinned && parent != TVI_ROOT ? TVI_FIRST : TVI_LAST;//parent != TVI_ROOT || type == 0 ? TVI_LAST : //abs(type) == 1 ? TVI_FIRST : treeItems[abs(type)]; tvins.hParent = parent; return (HTREEITEM)SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins); -}; +} + +LPARAM TreeView_GetItemParam (HWND hTreeWnd, HTREEITEM hItem) { + TVITEM ti = {0}; + ti.hItem = hItem; + ti.mask = TVIF_PARAM; + TreeView_GetItem(hTreeWnd, &ti); + + return ti.lParam; +} void updateTree(int type, TCHAR* select) { if (type < 0) { @@ -6109,6 +6258,12 @@ void updateTree(int type, TCHAR* select) { return; } + TCHAR treeFilter16[256] = {0}; + if (IsWindowVisible(hTreeSearchWnd)) { + GetWindowText(hTreeSearchWnd, treeFilter16, 255); + _tcslwr(treeFilter16); + } + TCHAR selText[256] = {0}; int selType = 0; if (type && select) { @@ -6176,7 +6331,7 @@ void updateTree(int type, TCHAR* select) { char* schema8 = utils::utf16to8(schema16); sprintf(sql, "select t.name, t.type, " \ - "iif(t.type in ('table', 'view'), group_concat(c.name || iif(t.type = 'table', ': ' || c.type || iif(c.pk,' [pk]',''), ''), ','), null) columns" \ + "iif(t.type in ('table', 'view'), group_concat(c.name || iif(t.type = 'table', ': ' || c.type || iif(c.pk,' [pk]',''), ''), ','), null) columns " \ "from \"%s\".sqlite_master t left join pragma_table_xinfo c on t.tbl_name = c.arg and c.schema = \"%s\" " \ "where t.sql is not null and t.type = coalesce(?1, t.type) and t.name <> 'sqlite_sequence' " \ "group by t.type, t.name order by t.type, t.name", @@ -6188,7 +6343,7 @@ void updateTree(int type, TCHAR* select) { conn = tabNo == -1 ? cli.db : tabs[tabNo].db; } - auto insertTreeItems = [conn, type, isObjectPinned, schema8](const char* sql, bool hasColumns) { + auto insertTreeItems = [conn, type, isObjectPinned, schema8, treeFilter16](const char* sql, bool hasColumns) { auto getItemType = [](const char* type8) { return !strcmp(type8, "table") ? TABLE : !strcmp(type8, "view") ? VIEW : @@ -6219,19 +6374,25 @@ void updateTree(int type, TCHAR* select) { TCHAR* name16 = utils::utf8to16(name8); TCHAR* columns16 = utils::utf8to16((char *) sqlite3_column_text(stmt, 2)); + bool isFilterColumn = false; bool pinned = (itemType == TABLE || itemType == VIEW) && isObjectPinned(name16); HTREEITEM hItem = TreeView_AddItem(name16, treeItems[itemType], itemType, 0, pinned); - if (pinned) { - SetProp(hTreeWnd, TEXT("LASTPINNED"), hItem); - prevItemType = itemType; - } - if (itemType == TABLE || itemType == VIEW) { if (hasColumns) { TCHAR* column16 = _tcstok (columns16, TEXT(",")); while (column16 != NULL) { - TreeView_AddItem(column16, hItem, itemType == TABLE ? COLUMN : 0); + HTREEITEM hColumnItem = TreeView_AddItem(column16, hItem, COLUMN, 0, true); + TCHAR* colName16 = _tcsdup(column16); + _tcslwr(colName16); + if (treeFilter16[0]) { + TCHAR* s = _tcsstr(colName16, treeFilter16); + if (s != 0 && (_tcschr(s, TEXT(':')) != 0)) { + TreeView_SetItemState(hTreeWnd, hColumnItem, TVIS_BOLD, TVIS_BOLD); + isFilterColumn = true; + } + } + free(colName16); column16 = _tcstok (NULL, TEXT(",")); } } else { @@ -6242,7 +6403,17 @@ void updateTree(int type, TCHAR* select) { sqlite3_bind_text(substmt, 3, schema8, strlen(schema8), SQLITE_TRANSIENT); while(SQLITE_ROW == sqlite3_step(substmt)) { TCHAR* column16 = utils::utf8to16((char *) sqlite3_column_text(substmt, 0)); - TreeView_AddItem(column16, hItem, COLUMN); + HTREEITEM hColumnItem = TreeView_AddItem(column16, hItem, COLUMN, 0, true); + TCHAR* colName16 = _tcsdup(column16); + _tcslwr(colName16); + if (treeFilter16[0]) { + TCHAR* s = _tcsstr(colName16, treeFilter16); + if (s != 0 && (_tcschr(s, TEXT(':')) != 0)) { + TreeView_SetItemState(hTreeWnd, hColumnItem, TVIS_BOLD, TVIS_BOLD); + isFilterColumn = true; + } + } + free(colName16); delete [] column16; } } @@ -6259,6 +6430,19 @@ void updateTree(int type, TCHAR* select) { } } + if (treeFilter16[0]) { + if (_tcsstr(name16, treeFilter16) == 0 && !isFilterColumn) + TreeView_DeleteItem(hTreeWnd, hItem); + + if (isFilterColumn) + TreeView_Expand(hTreeWnd, hItem, TVE_EXPAND); + } else { + if (pinned) { + SetProp(hTreeWnd, TEXT("LASTPINNED"), hItem); + prevItemType = itemType; + } + } + delete [] name16; delete [] columns16; } @@ -6573,7 +6757,7 @@ int ListView_SetData(HWND hListWnd, sqlite3_stmt *stmt, bool isRef) { SendMessage(hListWnd, WMU_RESET_CACHE, 0, 0); - int cacheSize = rowLimit > 0 ? rowLimit : 100 * (colCount + 1); + int cacheSize = (rowLimit > 0 ? rowLimit : 100) * (colCount + 1); TCHAR*** cache = isVirtual ? (TCHAR***)calloc(cacheSize, sizeof(TCHAR**)) : 0; byte* datatypes = (byte*)calloc(cacheSize, sizeof(byte)); unsigned char** blobs = isVirtual ? (unsigned char**)calloc(cacheSize, sizeof(unsigned char*)) : 0; @@ -6722,6 +6906,10 @@ int ListView_SetData(HWND hListWnd, sqlite3_stmt *stmt, bool isRef) { SetProp(hListWnd, TEXT("ROWCOUNT"), new int(rowCount)); SetProp(hListWnd, TEXT("TOTALROWCOUNT"), new int(rowCount)); + SetProp(hListWnd, TEXT("MODIFIERS"), (int*)calloc(colCount + 1, sizeof(int))); + SetProp(hListWnd, TEXT("MODIFIER_ROWS"), (int*)calloc(colCount + 1, sizeof(int))); + SetProp(hListWnd, TEXT("MODIFIERBUFFER"), new TCHAR[MAX_MODIFIER_OUTPUT_LEN + 1]); + ListView_SetItemCount(hListWnd, 0); SendMessage(hListWnd, WMU_UPDATE_RESULTSET, 0, 0); } @@ -6840,6 +7028,90 @@ bool ListView_DrillUp(HWND hListWnd) { return 1; } +BOOL ListView_GetItemValue(HWND hListWnd, int rowNo, int colNo, TValue* cell, bool ignoreResultset) { + TCHAR*** cache = (TCHAR***)GetProp(hListWnd, TEXT("CACHE")); + int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); + byte* datatypes = (byte*)GetProp(hListWnd, TEXT("DATATYPES")); + unsigned char** blobs = (unsigned char**)GetProp(hListWnd, TEXT("BLOBS")); + int colCount = ListView_GetColumnCount(hListWnd); + + if (!hListWnd || !cache || !datatypes || !blobs || colNo > colCount) + return FALSE; + + if (!ignoreResultset && !resultset) + rowNo = resultset[rowNo]; + + int no = colNo + rowNo * (colCount - 1); + int dataType = datatypes ? datatypes[no] : SQLITE_TEXT; + const unsigned char* blob = blobs && dataType == SQLITE_BLOB ? blobs[no] : 0; + + cell->dataType = dataType; + cell->dataLen = blob && dataType == SQLITE_BLOB ? utils::getBlobSize(blob) - 4 : _tcslen(cache[rowNo][colNo]); + cell->data = blob && dataType == SQLITE_BLOB ? blob + 4 : (const unsigned char*)cache[rowNo][colNo]; + + return TRUE; +} + +TCHAR* ListView_GetItemValueText(HWND hListWnd, int rowNo, int colNo, bool ignoreResultset) { + TCHAR* res16 = 0; + TCHAR*** cache = (TCHAR***)GetProp(hListWnd, TEXT("CACHE")); + int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); + + if (!cache) { + MessageBox(hMainWnd, TEXT("ListView_GetValueText allowed only for a virtual listview"), NULL, MB_OK); + return res16; + } + + if (!ignoreResultset) + rowNo = resultset[rowNo]; + + res16 = cache[rowNo][colNo]; // default behaviour + + int* modifiers = (int*)GetProp(hListWnd, TEXT("MODIFIERS")); + int modifierNo = modifiers ? modifiers[colNo] : 0; + TValue value = {0}; + if (modifierNo && ListView_GetItemValue(hListWnd, rowNo, colNo, &value, ignoreResultset)) { + TCHAR* buf16 = (TCHAR*)GetProp(hListWnd, TEXT("MODIFIERBUFFER")); + ZeroMemory(buf16, (MAX_MODIFIER_OUTPUT_LEN + 1) * sizeof(TCHAR)); + + if (modifierNo > 0 && plugins[modifierNo].hModule && plugins[modifierNo].type == ADDON_COLUMN_MODIFIER && plugins[modifierNo].setText) { + if (plugins[modifierNo].setText(hListWnd, colNo, value.data, value.dataLen, value.dataType, buf16)) + res16 = buf16; + } + + if (modifierNo < 0) { + modifierNo = abs(modifierNo); + sqlite3_stmt* stmt; + sqlite3_prepare_v2(prefs::db, "select code from functions where id = ?1", -1, &stmt, 0); + sqlite3_bind_int(stmt, 1, abs(modifierNo)); + if (SQLITE_ROW == sqlite3_step(stmt)) { + sqlite3_stmt* stmt2; + if (SQLITE_OK == sqlite3_prepare_v2(db, (const char*)sqlite3_column_text(stmt, 0), -1, &stmt2, 0)) { + if (value.dataType != SQLITE_BLOB) { + char* value8 = utils::utf16to8(cache[rowNo][colNo]); + dbutils::bind_variant(stmt2, 1, value8, false); + delete [] value8; + } else { + sqlite3_bind_blob(stmt2, 1, value.data, value.dataLen, SQLITE_TRANSIENT); + } + + if (SQLITE_ROW == sqlite3_step(stmt2)) { + TCHAR* value16 = utils::utf8to16((const char*)sqlite3_column_text(stmt2, 0)); + _sntprintf(buf16, MAX_MODIFIER_OUTPUT_LEN, value16); + delete [] value16; + + res16 = buf16; + } + } + sqlite3_finalize(stmt2); + } + sqlite3_finalize(stmt); + } + } + + return res16; +} + LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignoreLastColumn) { auto getRowLength = [hListWnd](int rowNo) { TCHAR rowLength[64 + 1]{0}; @@ -6854,13 +7126,11 @@ LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignore }; if (cmd == IDM_RESULT_CHART) - return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_CHART), hMainWnd, (DLGPROC)&dialogs::cbDlgChart, (LPARAM)hListWnd); + return DialogBoxParam(GetModuleHandle(0), MAKEINTRESOURCE(IDD_CHART), hMainWnd, (DLGPROC)dialogs::cbDlgChart, (LPARAM)hListWnd); if (cmd == IDM_RESULT_COPY_CELL) { - int rowLength = getRowLength(rowNo); - TCHAR buf[rowLength + 1]; - ListView_GetItemText(hListWnd, rowNo, colNo, buf, rowLength + 1); - utils::setClipboardText(buf); + TCHAR* value16 = ListView_GetItemValueText(hListWnd, rowNo, colNo); + utils::setClipboardText(value16); return true; } @@ -6869,11 +7139,11 @@ LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignore if ((GetWindowLongPtr(hHeader, GWL_STYLE) & HDS_FILTERBAR) == 0) SendMessage(hMainWnd, WM_COMMAND, IDM_RESULT_FILTERS, 0); - int rowLength = getRowLength(rowNo); - TCHAR buf[rowLength + 2]; - buf[0] = TEXT('='); - ListView_GetItemText(hListWnd, rowNo, colNo, buf + 1, rowLength + 1); - SetDlgItemText(hHeader, IDC_HEADER_EDIT + colNo, buf); + TCHAR* value16 = ListView_GetItemValueText(hListWnd, rowNo, colNo); + int len = _tcslen(value16) + 2; + TCHAR buf16[len]; + _sntprintf(buf16, len, TEXT("=%ls"), value16); + SetDlgItemText(hHeader, IDC_HEADER_EDIT + colNo, buf16); SendMessage(hListWnd, WMU_UPDATE_RESULTSET, 0, 0); // Resultset SendMessage(GetAncestor(hListWnd, GA_ROOT), WMU_UPDATE_DATA, 0, 0); // Edit data dialog } @@ -6885,7 +7155,7 @@ LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignore int colCount = (int)SendMessage(hHeader, HDM_GETITEMCOUNT, 0, 0L) - ignoreLastColumn; int rowCount = ListView_GetSelectedCount(hListWnd); int searchNext = LVNI_SELECTED; - if (cmd == IDM_RESULT_EXPORT && rowCount < 2) { + if (cmd == IDM_RESULT_EXPORT && rowCount < 2) { rowCount = ListView_GetItemCount(hListWnd); searchNext = LVNI_ALL; } @@ -6908,21 +7178,24 @@ LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignore } bool withHeader = cmd == IDM_RESULT_EXPORT || HIWORD(GetKeyState(VK_SHIFT)); - const TCHAR* delimiter16 = cmd == IDM_RESULT_COPY_ROW ? tools::DELIMITERS[0] : tools::DELIMITERS[prefs::get("csv-export-delimiter")]; + const TCHAR* delimiter16 = tools::DELIMITERS[prefs::get(cmd == IDM_RESULT_COPY_ROW ? "copy-to-clipboard-delimiter" : "csv-export-delimiter")]; const TCHAR* newLine16 = cmd == IDM_RESULT_COPY_ROW || !prefs::get("csv-export-is-unix-line") ? TEXT("\r\n") : TEXT("\n"); if (!colCount || !rowCount) return false; + int* modifiers = (int*)GetProp(hListWnd, TEXT("MODIFIERS")); int bufSize = withHeader ? 255 * colCount : 0; int currRowNo = -1; - while((currRowNo = ListView_GetNextItem(hListWnd, currRowNo, searchNext)) != -1) + while((currRowNo = ListView_GetNextItem(hListWnd, currRowNo, searchNext)) != -1) { bufSize += getRowLength(currRowNo); + for (int colNo = 1; colNo < colCount; colNo++) + bufSize += modifiers && modifiers[colNo] ? _tcslen(ListView_GetItemValueText(hListWnd, currRowNo, colNo)) : 0; + } bufSize += (colCount /* delimiters*/ + 64 /* possible quotes */ + 3 /* new line */) * rowCount + 1 /* EOF */; bufSize *= 2; // single " is extended to "" => allocate additional memory TCHAR* res16 = new TCHAR[bufSize] {0}; - TCHAR buf16[MAX_TEXT_LENGTH]{0}; auto addValue = [](TCHAR* res16, TCHAR* buf16) { TCHAR* qvalue16 = utils::replaceAll(buf16, TEXT("\""), TEXT("\"\"")); @@ -6939,7 +7212,8 @@ LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignore if (withHeader) { for(int colNo = 1; colNo < colCount; colNo++) { - Header_GetItemText(hHeader, colNo, buf16, MAX_TEXT_LENGTH); + TCHAR buf16[256]{0}; + Header_GetItemText(hHeader, colNo, buf16, 255); addValue(res16, buf16); _tcscat(res16, colNo != colCount - 1 ? delimiter16 : newLine16); } @@ -6948,8 +7222,8 @@ LRESULT onListViewMenu(HWND hListWnd, int rowNo, int colNo, int cmd, bool ignore currRowNo = -1; while((currRowNo = ListView_GetNextItem(hListWnd, currRowNo, searchNext)) != -1) { for(int colNo = 1; colNo < colCount; colNo++) { - ListView_GetItemText(hListWnd, currRowNo, colNo, buf16, MAX_TEXT_LENGTH); - addValue(res16, buf16); + TCHAR* value16 = ListView_GetItemValueText(hListWnd, currRowNo, colNo); + addValue(res16, value16); _tcscat(res16, colNo != colCount - 1 ? delimiter16 : newLine16); } } @@ -7423,6 +7697,24 @@ BOOL Menu_SetItemText(HMENU hMenu, UINT wID, const TCHAR* caption) { return SetMenuItemInfo(hMenu, wID, FALSE, &mii); } +BOOL Menu_SetItemData(HMENU hMenu, UINT wID, ULONG_PTR lParam) { + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_DATA; + mii.dwItemData = lParam; + + return SetMenuItemInfo(hMenu, wID, FALSE, &mii); +} + +ULONG_PTR Menu_GetItemData(HMENU hMenu, UINT wID) { + MENUITEMINFO mii = {0}; + mii.cbSize = sizeof(MENUITEMINFO); + mii.fMask = MIIM_DATA; + + GetMenuItemInfo(hMenu, wID, FALSE, &mii); + return mii.dwItemData; +} + BOOL Menu_SetItemState(HMENU hMenu, UINT wID, UINT fState) { MENUITEMINFO mii = {0}; mii.cbSize = sizeof(MENUITEMINFO); @@ -8009,9 +8301,10 @@ bool processAutoComplete(HWND hEditorWnd, int key, bool isKeyDown) { int cPos = crPos; // fix new line \r \n - for (int i = 0; (i < cPos) && (i < tLen + 1); i++) + for (int i = 0; (i < cPos) && (i < tLen + 1); i++) { if (text[i] == TEXT('\r')) cPos = cPos + 1; + } // qStart qEnd // | | @@ -8074,22 +8367,51 @@ bool processAutoComplete(HWND hEditorWnd, int key, bool isKeyDown) { TCHAR query16[qEnd - qStart + 1]{0}; _tcsncpy(query16, text + qStart, qEnd - qStart); + _tcslwr(query16); + if (_tcslen(query16) > 0) { - sqlite3_stmt *stmt; - char sql8[] = - "select c.name from sqlite_master t left join pragma_table_xinfo c on t.tbl_name = c.arg and c.schema = 'main' " \ - "where t.sql is not null and t.type in ('table', 'view') and ?1 regexp '\\b' || lower(t.tbl_name) || '\\b' order by t.tbl_name, c.cid"; + auto hasWord = [](TCHAR* str, TCHAR* word) { + bool res = false; + + TCHAR breakers[] = TEXT(" \"'`\n\t+-;:(),=<>/%"); + TCHAR* s = _tcsstr(str, word); + int slen = _tcslen(str); + int wlen = _tcslen(word); + + while (!res && s) { + int l = slen - _tcslen(s); + int r = l + wlen; + res = (l == 0 || (l > 0 && _tcschr(breakers, str[l - 1]))) && (r == slen || (r < slen && _tcschr(breakers, str[r]))); + s = _tcsstr(s + 1, word); + } - if (SQLITE_OK == sqlite3_prepare_v2(db, sql8, -1, &stmt, 0)) { - char* query8 = utils::utf16to8(query16); - sqlite3_bind_text(stmt, 1, query8, strlen(query8), SQLITE_TRANSIENT); - delete [] query8; + return res; + }; + // Each table is processed separately because pragma_table_xinfo-command can raise an error + // hasWord-function is used instead of regexp \b\b because tbl_name can contain special char for regexp e.g. $ + sqlite3_stmt *stmt; + if (SQLITE_OK == sqlite3_prepare_v2(db, "select lower(tbl_name) from main.sqlite_master where type in ('table', 'view') and sql is not null order by tbl_name", -1, &stmt, 0)) { bool isNoCheck = wLen == 0; + while (SQLITE_ROW == sqlite3_step(stmt)) { - TCHAR* name16 = utils::utf8to16((char *) sqlite3_column_text(stmt, 0)); - isExact += addString(name16, isNoCheck); - delete [] name16; + const char* tblName8 = (const char*) sqlite3_column_text(stmt, 0); + TCHAR* tblName16 = utils::utf8to16(tblName8); + if (hasWord(query16, tblName16)) { + sqlite3_stmt *stmt2; + if (SQLITE_OK == sqlite3_prepare_v2(db, "select name from pragma_table_xinfo where schema = 'main' and arg = ?1", -1, &stmt2, 0)) { + sqlite3_bind_text(stmt2, 1, tblName8, strlen(tblName8), SQLITE_TRANSIENT); + + while (SQLITE_ROW == sqlite3_step(stmt2)) { + TCHAR* name16 = utils::utf8to16((char *) sqlite3_column_text(stmt2, 0)); + isExact += addString(name16, isNoCheck); + delete [] name16; + } + } + sqlite3_finalize(stmt2); + } + + delete [] tblName16; } } sqlite3_finalize(stmt); @@ -8352,29 +8674,35 @@ LRESULT CALLBACK cbNewResultTab(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara LV_ITEM* pItem = &(pDispInfo)->item; HWND hListWnd = pHdr->hwndFrom; - TCHAR*** cache = (TCHAR***)GetProp(hListWnd, TEXT("CACHE")); - int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); - if(cache && resultset && pItem->mask & LVIF_TEXT) { - int rowNo = resultset[pItem->iItem]; - pItem->pszText = cache[rowNo][pItem->iSubItem]; - } + if (pItem->mask & LVIF_TEXT) + pItem->pszText = ListView_GetItemValueText(hListWnd, pItem->iItem, pItem->iSubItem); } if (pHdr->code == (UINT)NM_CUSTOMDRAW) { int result = CDRF_DODEFAULT; HWND hListWnd = pHdr->hwndFrom; + int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); byte* datatypes = (byte*)GetProp(hListWnd, TEXT("DATATYPES")); - COLORREF* heatmap = (COLORREF*)GetProp(hListWnd, TEXT("HEATMAP")); + int* modifiers = (int*)GetProp(hListWnd, TEXT("MODIFIERS")); + if (!datatypes) - return result; + return CDRF_DODEFAULT; NMLVCUSTOMDRAW* pCustomDraw = (LPNMLVCUSTOMDRAW)lParam; + int rowNo = pCustomDraw->nmcd.dwItemSpec; + int colNo = pCustomDraw->iSubItem; + int colCount = ListView_GetColumnCount(hListWnd); + + + int modifierNo = modifiers && colNo >= 0 && colNo < colCount ? modifiers[colNo] : 0; // ??? Some colNo less 0 or greater colCount + modifierNo = modifierNo > 0 && modifierNo < MAX_PLUGIN_COUNT && plugins[modifierNo].hModule && plugins[modifierNo].type == ADDON_COLUMN_MODIFIER ? modifierNo : 0; + if (pCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT) result = CDRF_NOTIFYITEMDRAW; if (pCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) { pCustomDraw->nmcd.lItemlParam = 0; - if (ListView_GetItemState(pHdr->hwndFrom, pCustomDraw->nmcd.dwItemSpec, LVIS_SELECTED)) { + if (ListView_GetItemState(hListWnd, rowNo, LVIS_SELECTED)) { pCustomDraw->nmcd.uItemState &= ~CDIS_SELECTED; pCustomDraw->nmcd.lItemlParam = 1; } @@ -8382,11 +8710,7 @@ LRESULT CALLBACK cbNewResultTab(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara } if (pCustomDraw->nmcd.dwDrawStage == (CDDS_ITEMPREPAINT | CDDS_SUBITEM)) { - int rowNo = pCustomDraw->nmcd.dwItemSpec; - int colNo = pCustomDraw->iSubItem; int colCount = Header_GetItemCount(ListView_GetHeader(hListWnd)) - 1; - - int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); rowNo = resultset[rowNo]; bool isFocus = GetFocus() == hListWnd; @@ -8399,9 +8723,20 @@ LRESULT CALLBACK cbNewResultTab(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara pCustomDraw->clrTextBk = isCurrCell ? GRIDCOLORS[0] : isSelectedRow && isFocus ? RGB(10, 36, 106) : isSelectedRow && !isFocus ? RGB(212, 208, 200) : - heatmap ? heatmap[colNo + colCount * rowNo] : datatypes ? GRIDCOLORS[datatypes[colNo + colCount * rowNo]] : RGB(255, 255, 255); + + if (modifierNo && (plugins[modifierNo].setColor || plugins[modifierNo].render)) { + TValue value = {0}; + if (ListView_GetItemValue(hListWnd, rowNo, colNo, &value)) { + if (plugins[modifierNo].setColor) + plugins[modifierNo].setColor(pCustomDraw, value.data, value.dataLen, value.dataType); + + if (plugins[modifierNo].render) { + result = plugins[modifierNo].render(pCustomDraw, value.data, value.dataLen, value.dataType) ? CDRF_SKIPDEFAULT : result; + } + } + } } return result; @@ -8479,27 +8814,13 @@ LRESULT CALLBACK cbNewResultTab(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lPara SetProp(hListWnd, TEXT("CURRENTROW"), IntToPtr(rowNo)); SetProp(hListWnd, TEXT("CURRENTCOLUMN"), IntToPtr(colNo)); - if (prefs::get("show-preview") && rowNo != -1 && colNo != -1) { - byte* datatypes = (byte*)GetProp(hListWnd, TEXT("DATATYPES")); - int colCount = Header_GetItemCount(ListView_GetHeader(hListWnd)) - 1; - - int* resultset = (int*)GetProp(hListWnd, TEXT("RESULTSET")); - TCHAR*** cache = (TCHAR***)GetProp(hListWnd, TEXT("CACHE")); - byte type = datatypes ? datatypes[colNo + colCount * resultset[rowNo]] : SQLITE_TEXT; + TValue value = {0}; + if (prefs::get("show-preview") && rowNo != -1 && colNo != -1 && ListView_GetItemValue(hListWnd, rowNo, colNo, &value)) { + SetProp(hPreviewWnd, TEXT("DATA"), (HANDLE)value.data); + SetProp(hPreviewWnd, TEXT("DATATYPE"), IntToPtr(value.dataType)); + SetProp(hPreviewWnd, TEXT("DATALEN"), IntToPtr(value.dataLen)); - if (type == SQLITE_TEXT || type == SQLITE_FLOAT || type == SQLITE_INTEGER) { - TCHAR* value16 = cache[resultset[rowNo]][colNo]; - SendMessage(hPreviewWnd, WMU_UPDATE_PREVIEW, SQLITE_TEXT, (LPARAM)value16); - } - - if (type == SQLITE_NULL) - SendMessage(hPreviewWnd, WMU_UPDATE_PREVIEW, SQLITE_NULL, 0); - - if (type == SQLITE_BLOB) { - char** blobs = (char**)GetProp(hListWnd, TEXT("BLOBS")); - char* data = blobs ? blobs[colNo + colCount * resultset[rowNo]] : 0; - SendMessage(hPreviewWnd, WMU_UPDATE_PREVIEW, SQLITE_BLOB, (LPARAM)data); - } + SendMessage(hPreviewWnd, WMU_UPDATE_PREVIEW, 0, 0); } } break; @@ -9538,7 +9859,7 @@ void createResultControls(HWND hTabWnd, int resultNo) { CreateWindow(WC_LISTBOX, NULL, WS_CHILD, 300, 0, 400, 100, hRowsWnd, (HMENU)IDC_REFLIST, GetModuleHandle(0), 0); HWND hPreviewWnd = CreateWindow(WC_STATIC, NULL, WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | SS_CENTER | WS_BORDER | SS_CENTERIMAGE, 20, 20, 100, 100, hTabWnd, (HMENU)IntToPtr(IDC_TAB_PREVIEW + resultNo), GetModuleHandle(0), NULL); - SetProp(hPreviewWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hPreviewWnd, GWLP_WNDPROC, (LONG_PTR)&dialogs::cbNewValueViewer)); + SetProp(hPreviewWnd, TEXT("WNDPROC"), (HANDLE)SetWindowLongPtr(hPreviewWnd, GWLP_WNDPROC, (LONG_PTR)dialogs::cbNewValueViewer)); SetProp(hPreviewWnd, TEXT("INFO"), (HANDLE)(new TCHAR[255])); SetProp(hPreviewWnd, TEXT("EXT"), (HANDLE)(new TCHAR[32])); SendMessage(hPreviewWnd, WM_SETFONT, (LPARAM)hFont, 0); @@ -9650,8 +9971,7 @@ bool saveResultToTable(sqlite3* db, const TCHAR* table16, int tabNo, int resultN while(rc && (rowNo = ListView_GetNextItem(hListWnd, rowNo, searchNext)) != -1) { for(int colNo = 1; colNo < colCount; colNo++) { - TCHAR val16[MAX_TEXT_LENGTH + 1]; - ListView_GetItemText(hListWnd, rowNo, colNo, val16, MAX_TEXT_LENGTH); + TCHAR* val16 = ListView_GetItemValueText(hListWnd, rowNo, colNo); int no = colNo + resultset[rowNo] * (colCount - 1); const unsigned char* blob = datatypes && blobs && datatypes[no] == SQLITE_BLOB ? blobs[no] : 0; if (!blob) { diff --git a/src/prefs.cpp b/src/prefs.cpp index f923660..9c842f6 100644 --- a/src/prefs.cpp +++ b/src/prefs.cpp @@ -5,7 +5,7 @@ namespace prefs { sqlite3* db = NULL; - const int ICOUNT = 88; + const int ICOUNT = 89; const char* iprops[ICOUNT] = { "x", "y", "width", "height", "splitter-position-x", "splitter-position-y", "maximized", "font-size", "max-query-count", "exit-by-escape", "beep-query-duration", "synchronous-off", @@ -14,7 +14,7 @@ namespace prefs { "use-autocomplete", "autocomplete-by-tab", "disable-autocomplete-help", "use-foreign-keys", "use-legacy-rename", "editor-indent", "editor-tab-count", "editor-tab-current", "highlight-delay", "ask-delete", "word-wrap", "clear-values", "recent-count", "auto-filters", "edit-data-filter-mode", - "csv-export-is-unix-line", "csv-export-delimiter", "csv-export-is-columns", + "csv-export-is-unix-line", "csv-export-delimiter", "csv-export-is-columns", "copy-to-clipboard-delimiter", "csv-import-encoding", "csv-import-delimiter", "csv-import-is-columns", "csv-import-is-create-table", "csv-import-is-truncate", "csv-import-is-replace", "csv-import-trim-values", "csv-import-skip-empty", "csv-import-abort-on-error", "odbc-strategy", "sql-export-multiple-insert", "extended-query-plan", @@ -41,7 +41,7 @@ namespace prefs { 1, 1, 0, 0, 0, 0, // use 1, 0, 30, 0, 0, 0, 10, 1, 0, - 0, 0, 1, + 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, // csv-import 0, 0, 0, diff --git a/src/resource.h b/src/resource.h index 715dea3..9579c3f 100644 --- a/src/resource.h +++ b/src/resource.h @@ -1,5 +1,5 @@ -#define GUI_VERSION "1.9.1" -#define GUI_VERSION2 1, 9, 1, 0 +#define GUI_VERSION "1.9.2" +#define GUI_VERSION2 1, 9, 2, 0 #ifdef __MINGW64__ #define GUI_PLATFORM 64 #else @@ -9,6 +9,7 @@ #define EXTENSION_REPOSITORY "little-brother/sqlite-extensions" #define EXTENSION_DIRECTORY "\\extensions\\" #define VIEWER_REPOSITORY "little-brother/sqlite-gui-value-viewers" +#define MODIFIER_REPOSITORY "little-brother/sqlite-gui-column-modifiers" #define PLUGIN_DIRECTORY "\\plugins\\" #define IDD_ADDVIEWEDIT 11 @@ -88,12 +89,13 @@ #define IDC_EDITOR 263 #define IDC_TAB 264 #define IDC_TREE 265 -#define IDC_SCHEMA 266 -#define IDC_EDITOR_SEARCH 267 -#define IDC_EDITOR_SEARCH_STRING 268 -#define IDC_EDITOR_SEARCH_PREV 269 -#define IDC_EDITOR_SEARCH_NEXT 270 -#define IDC_EDITOR_SEARCH_CLOSE 271 +#define IDC_TREE_SEARCH 266 +#define IDC_SCHEMA 267 +#define IDC_EDITOR_SEARCH 270 +#define IDC_EDITOR_SEARCH_STRING 271 +#define IDC_EDITOR_SEARCH_PREV 272 +#define IDC_EDITOR_SEARCH_NEXT 273 +#define IDC_EDITOR_SEARCH_CLOSE 274 #define IDC_CLI_EDITOR 275 #define IDC_CLI_RESULT 276 #define IDC_CLI_RAWDATA 277 @@ -261,10 +263,13 @@ #define IDC_DLG_DATASET_NAME 482 #define IDC_DLG_DATASET 483 #define IDC_DLG_ADDON_LIST 484 +#define IDC_DLG_DELIMITER_LABEL 485 #define IDC_DLG_EXTENSION_REPOSITORY 486 #define IDC_DLG_EXTENSION_REPOSITORY_LABEL 487 #define IDC_DLG_VIEWER_REPOSITORY 488 #define IDC_DLG_VIEWER_REPOSITORY_LABEL 489 +#define IDC_DLG_MODIFIER_REPOSITORY 490 +#define IDC_DLG_MODIFIER_REPOSITORY_LABEL 491 #define IDC_DLG_CIPHER_KEY 601 #define IDC_DLG_CIPHER_SHOW_KEY 602 @@ -322,10 +327,11 @@ #define IDM_ATTACH_ODBC 1005 #define IDM_ENCRYPTION 1006 #define IDM_VIEWER_PLUGINS 1007 -#define IDM_EXTENSIONS 1008 -#define IDM_CUSTOM_FUNCTIONS 1009 -#define IDM_SETTINGS 1010 -#define IDM_EXIT 1011 +#define IDM_MODIFIER_PLUGINS 1008 +#define IDM_EXTENSIONS 1009 +#define IDM_CUSTOM_FUNCTIONS 1020 +#define IDM_SETTINGS 1021 +#define IDM_EXIT 1022 #define IDM_RECENT 1100 // iterable #define IDM_RECENT_ATTACHED 1150 // iterable @@ -410,15 +416,11 @@ #define IDM_RESULT_EXPORT 1627 #define IDM_RESULT_EXCEL 1628 #define IDM_RESULT_TRANSPOSE 1629 -#define IDM_RESULT_HEATMAP 1630 -//#define IDM_BLOB_VIEW 1680 -#define IDM_VALUE_FILE_OPEN 1681 -#define IDM_VALUE_FILE_SAVE 1682 -#define IDM_VALUE_FILE_SET 1683 -#define IDM_VALUE_FILE_EDIT 1684 -// #define IDM_BLOB_IMPORT 1682 -// #define IDM_BLOB_EXPORT 1683 +#define IDM_VALUE_FILE_OPEN 1681 +#define IDM_VALUE_FILE_SAVE 1682 +#define IDM_VALUE_FILE_SET 1683 +#define IDM_VALUE_FILE_EDIT 1684 #define IDM_DEMODB_BOOKSTORE 1685 #define IDM_DEMODB_CHINOOK 1686 @@ -468,6 +470,7 @@ #define IDM_EXPORT_FILE 1731 #define IDM_FILTER_TYPE 1732 #define IDM_COMPARE_TEXTS 1733 +#define IDM_CHART_RESET 1734 #define IDM_CLI_COPY 1740 #define IDM_CLI_CUT 1741 @@ -478,9 +481,10 @@ #define IDM_TEST 1760 -#define IDM_RESULT_COMPARE 1800 // iterable, 50 -#define IDM_MENU_TRIGGER 1900 // iterable, 50 -#define IDM_MENU_INDEX 1950 // iterable, 50 +#define IDM_RESULT_COMPARE 1800 // iterable, 100 +#define IDM_RESULT_MODIFIER 1900 // iterable, MAX_PLUGIN_COUNT +#define IDM_MENU_TRIGGER 2200 // iterable, 50 +#define IDM_MENU_INDEX 2250 // iterable, 50 // Iterable. Should have a gap. #define IDC_HEADER_EDIT 2500 @@ -586,7 +590,6 @@ #define WMU_UPDATE_SB_RESULTSET WM_USER + 50 #define WMU_RESULT_SEARCH WM_USER + 51 #define WMU_COMPARE WM_USER + 52 -#define WMU_HEATMAP WM_USER + 53 #define WMU_UPDATE_SHEET_IDS WM_USER + 54 #define WMU_UPDATE_SHEET_PREVIEW WM_USER + 55 #define WMU_SET_THEME WM_USER + 56 @@ -595,7 +598,8 @@ #define WMU_UPDATE_META WM_USER + 59 #define WMU_SET_SCROLL_HEIGHT WM_USER + 60 #define WMU_ADD_EMPTY_ROW WM_USER + 61 -#define WMU_TEST WM_USER + 62 +#define WMU_TEST WM_USER + 62 +#define WMU_RESET_MODIFIER WM_USER + 63 // ricedit.h has own WM_USER + N message, but N less 210 #define WMU_HIGHLIGHT WM_USER + 260 diff --git a/src/resource.rc b/src/resource.rc index b08a0f4..aff854d 100644 --- a/src/resource.rc +++ b/src/resource.rc @@ -170,12 +170,12 @@ BEGIN END LANGUAGE 0, SUBLANG_NEUTRAL -IDD_SETTINGS DIALOGEX 0, 0, 215, 230 +IDD_SETTINGS DIALOGEX 0, 0, 215, 240 STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | DS_MODALFRAME | WS_CLIPCHILDREN CAPTION "Settings" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CONTROL "", IDC_DLG_SETTING_TAB, WC_TABCONTROL, TCS_FIXEDWIDTH, 5, 5, 205, 200, WS_EX_CONTROLPARENT + CONTROL "", IDC_DLG_SETTING_TAB, WC_TABCONTROL, TCS_FIXEDWIDTH, 5, 5, 205, 210, WS_EX_CONTROLPARENT AUTOCHECKBOX "Open last database on startup", IDC_DLG_RESTORE_DB, 5, 3, 180, 14, BS_NOTIFY | WS_TABSTOP AUTOCHECKBOX "Restore editor tabs on startup", IDC_DLG_RESTORE_EDITOR, 5, 18, 180, 14, BS_NOTIFY | WS_TABSTOP @@ -226,23 +226,29 @@ BEGIN LTEXT "Beep if the query took longer than N ms", IDC_DLG_BEEP_LABEL, 5, 126, 150, 12, SS_LEFT EDITTEXT IDC_DLG_BEEP_ON_QUERY_END, 160, 124, 35, 13, WS_BORDER | WS_TABSTOP | ES_CENTER | ES_NUMBER + LTEXT "Column delimiter when copy rows to clipboard", IDC_DLG_DELIMITER_LABEL, 5, 41 + 100, 150, 14, SS_LEFT + COMBOBOX IDC_DLG_DELIMITER, 160, 39 + 100, 33, 100, WS_TABSTOP | WS_VSCROLL | CBS_DROPDOWNLIST | CBS_HASSTRINGS + LTEXT "Execute on a database connection", IDC_DLG_STARTUP_LABEL, 5, 5, 150, 12, SS_LEFT - EDITTEXT IDC_DLG_STARTUP, 5, 17, 190, 40, WS_BORDER | WS_TABSTOP | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + EDITTEXT IDC_DLG_STARTUP, 5, 17, 190, 30, WS_BORDER | WS_TABSTOP | ES_MULTILINE | ES_AUTOHSCROLL | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL + + AUTOCHECKBOX "Use foreign keys (pragma foreign_keys = on)", IDC_DLG_FOREIGN_KEYS, 5, 50, 190, 14, BS_NOTIFY | WS_TABSTOP + AUTOCHECKBOX "Use unsafe renaming (pragma legacy_alter_table = on)", IDC_DLG_LEGACY_RENAME, 5, 61, 190, 14, BS_NOTIFY | WS_TABSTOP - AUTOCHECKBOX "Use foreign keys (pragma foreign_keys = on)", IDC_DLG_FOREIGN_KEYS, 5, 60, 190, 14, BS_NOTIFY | WS_TABSTOP - AUTOCHECKBOX "Use unsafe renaming (pragma legacy_alter_table = on)", IDC_DLG_LEGACY_RENAME, 5, 71, 190, 14, BS_NOTIFY | WS_TABSTOP + LTEXT "Google API key", IDC_DLG_GOOGLE_KEY_LABEL, 5, 80, 150, 10, SS_LEFT + EDITTEXT IDC_DLG_GOOGLE_KEY, 5, 90, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN - LTEXT "Google API key", IDC_DLG_GOOGLE_KEY_LABEL, 5, 90, 150, 10, SS_LEFT - EDITTEXT IDC_DLG_GOOGLE_KEY, 5, 100, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN + LTEXT "Value viewers respository", IDC_DLG_VIEWER_REPOSITORY_LABEL, 5, 108, 152, 10, SS_LEFT + EDITTEXT IDC_DLG_VIEWER_REPOSITORY, 5, 118, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN - LTEXT "Viewer plugin respository", IDC_DLG_VIEWER_REPOSITORY_LABEL, 5, 118, 152, 10, SS_LEFT - EDITTEXT IDC_DLG_VIEWER_REPOSITORY, 5, 128, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN + LTEXT "Column modifiers respository", IDC_DLG_MODIFIER_REPOSITORY_LABEL, 5, 108 + 28, 152, 10, SS_LEFT + EDITTEXT IDC_DLG_MODIFIER_REPOSITORY, 5, 118 + 28, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN - LTEXT "SQLite extension respository", IDC_DLG_EXTENSION_REPOSITORY_LABEL, 5, 146, 152, 10, SS_LEFT - EDITTEXT IDC_DLG_EXTENSION_REPOSITORY, 5, 156, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN + LTEXT "SQLite extension respository", IDC_DLG_EXTENSION_REPOSITORY_LABEL, 5, 146 + 18, 152, 10, SS_LEFT + EDITTEXT IDC_DLG_EXTENSION_REPOSITORY, 5, 156 + 18, 190, 11, WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | ES_WANTRETURN - PUSHBUTTON "OK", IDC_DLG_OK, 86, 211, 60, 14, WS_TABSTOP - PUSHBUTTON "Cancel", IDC_DLG_CANCEL, 150, 211, 60, 14, WS_TABSTOP + PUSHBUTTON "OK", IDC_DLG_OK, 86, 221, 60, 14, WS_TABSTOP + PUSHBUTTON "Cancel", IDC_DLG_CANCEL, 150, 221, 60, 14, WS_TABSTOP END LANGUAGE 0, SUBLANG_NEUTRAL @@ -378,7 +384,7 @@ END LANGUAGE 0, SUBLANG_NEUTRAL IDD_ADDON_MANAGER DIALOGEX 100, 50, 400, 300 STYLE WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SIZEBOX | DS_MODALFRAME - CAPTION "Addon manager" + CAPTION "Add-on manager" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN CONTROL "", IDC_DLG_ADDON_LIST, WC_LISTVIEW, WS_TABSTOP| WS_CLIPSIBLINGS | LVS_AUTOARRANGE | LVS_REPORT | LVS_NOSORTHEADER | LVS_SHOWSELALWAYS | LVS_SINGLESEL | WS_TABSTOP, 0, 0, 400, 300, WS_EX_STATICEDGE @@ -827,8 +833,9 @@ BEGIN POPUP "Add-ons" BEGIN - MENUITEM "Viewer plugins", IDM_VIEWER_PLUGINS - MENUITEM "SQlite extensions", IDM_EXTENSIONS + MENUITEM "Value viewer plugins", IDM_VIEWER_PLUGINS + MENUITEM "Column modifier plugins", IDM_MODIFIER_PLUGINS + MENUITEM "SQLite extensions", IDM_EXTENSIONS END POPUP "?" @@ -999,7 +1006,11 @@ BEGIN MENUITEM "View as chart", IDM_RESULT_CHART MENUITEM "Filter by value", IDM_RESULT_VALUE_FILTER MENUITEM "Transpose", IDM_RESULT_TRANSPOSE - MENUITEM "Heat map", IDM_RESULT_HEATMAP + MENUITEM SEPARATOR + POPUP "Column modifier" + BEGIN + MENUITEM "No plugin installed", IDM_RESULT_MODIFIER, GRAYED + END MENUITEM SEPARATOR MENUITEM "Save as table", IDM_RESULT_AS_TABLE MENUITEM "Export to csv", IDM_RESULT_EXPORT @@ -1087,6 +1098,8 @@ BEGIN BEGIN MENUITEM "Copy to clipboard", IDM_EXPORT_CLIPBOARD MENUITEM "Save as .png", IDM_EXPORT_PNG + MENUITEM SEPARATOR + MENUITEM "Reset zoom", IDM_CHART_RESET END END @@ -1166,6 +1179,7 @@ BEGIN "Ctrl + Alt + Escape/Backspace - Close the current editor\n" \ "Ctrl + Alt + Left/Right - Toggle the editor's tab or switch between data windows\n\n" \ "Navigation tree\n" \ + "Ctrl + F - show find-in-tree filter\n" \ "Ctrl + C - copy the text of the selected item to the clipboard\n" \ "Ctrl + Shift + C - copy the DDL of the selected item to the clipboard\n\n" \ "Editor\n" \ @@ -1231,11 +1245,9 @@ BEGIN " Use >N or \n\n" \ - "* Copy and Export result rows operations use the same settings as Tools >\n Export as CSV file. Settings are updated when you export any table.\n\n" \ "* To import Excel data, copy it to clipboard and paste to an editor\n\n" \ "* A beep when a database is opened indicates that REST API is available" @@ -1277,7 +1289,8 @@ BEGIN "You can install exec-extension and use it to call non-SQL functions e.g. \n"\ "select exec('powershell -nologo ""Get-Content ?1""')\n\n" "Be aware, these functions exist only in the sqlite-gui and they are \n" \ - "very slow. Only scalar functions are supported." + "very slow. Only scalar functions are supported.\n\n" \ + "Single argument function can be used as a column modifier.\n" IDS_TEMP_EXPLAIN "Each tab uses an own independed connection to the database and hence " \ "has own TEMP-schema (SQLite limitation). The navigation tree shows TEMP-schema of a current tab. " \ diff --git a/src/tools.cpp b/src/tools.cpp index 0af3e71..934d167 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -2672,7 +2672,7 @@ namespace tools { return false; } -// lParam = TDlgParam + // lParam = TDlgParam BOOL CALLBACK cbDlgTextComparison (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch (msg) { case WM_INITDIALOG: { @@ -3137,17 +3137,16 @@ namespace tools { delete [] delete8; fclose(f); - if (rc && isAutoTransaction) - sqlite3_exec(db, rc ? "commit" : "rollback", NULL, 0, NULL); - if (!rc) { TCHAR* _err16 = utils::utf8to16(sqlite3_errmsg(db)); _sntprintf(err16, 1023, TEXT("Error: %s"), _err16); delete [] _err16; - return -1; } - return rowNo; + if (isAutoTransaction) + sqlite3_exec(db, rc ? "commit" : "rollback", NULL, 0, NULL); + + return rc ? rowNo : -1; } int exportCSV(TCHAR* path16, TCHAR* query16, TCHAR* err16) { diff --git a/src/utils.cpp b/src/utils.cpp index f6416a5..9dbde58 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -91,11 +91,16 @@ namespace utils { bool hasString(const TCHAR* str, const TCHAR* sub) { bool res = false; + if (!str || _tcslen(str) == 0 || !sub || _tcslen(sub) == 0) + return res; + TCHAR* lstr = _tcsdup(str); _tcslwr(lstr); TCHAR* lsub = _tcsdup(sub); _tcslwr(lsub); + res = _tcsstr(lstr, lsub) != 0; + free(lstr); free(lsub); @@ -415,7 +420,7 @@ namespace utils { d = _tcstod(str, &endptr); bool rc = !(errno != 0 || *endptr != '\0'); if (rc && out != NULL) - *out = d; + *out = d; if (rc) return true; @@ -941,6 +946,18 @@ namespace utils { float z = getWndScale(hWnd).x; return (13 - (z >= 1.25f) - (z >= 1.5f)) * getDlgScale(hWnd).y; } + int getEditHeightByFont(HWND hWnd) { + RECT rc = {0}; + HFONT hFont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0); + + HDC hDC = GetDC(hWnd); + HFONT hOldFont = (HFONT)SelectObject(hDC, hFont); + DrawText(hDC, TEXT("AgqW"), 5, &rc, DT_CALCRECT); + SelectObject(hDC, hOldFont); + ReleaseDC(hWnd, hDC); + + return rc.bottom; + } void alignDialog(HWND hDlgWnd, HWND hParentWnd, bool doLess, bool doMore) { RECT rc, prc; diff --git a/src/utils.h b/src/utils.h index 481ccbd..9f49cbb 100644 --- a/src/utils.h +++ b/src/utils.h @@ -65,6 +65,7 @@ namespace utils { POINTFLOAT getDlgScale(HWND hWnd); POINTFLOAT getWndScale(HWND hWnd); int getEditHeight(HWND hWnd); + int getEditHeightByFont(HWND hWnd); void alignDialog(HWND hDlgWnd, HWND hParentWnd, bool doLess = false, bool doMore = false); int getBlobSize (const unsigned char* data);