diff --git a/.github/workflows/lazarus.yaml b/.github/workflows/lazarus.yaml index 82bee9b6a..30f989669 100644 --- a/.github/workflows/lazarus.yaml +++ b/.github/workflows/lazarus.yaml @@ -130,6 +130,49 @@ jobs: name: ${{ matrix.operating-system }} path: out/macos/heidisql + build-qt6arm: + name: Linux ARM64 QT6 + runs-on: ubuntu-24.04-arm + env: + GITHUB: 1 + + steps: + - name: Checkout source code + uses: actions/checkout@v4 + + # Install FPC + Lazarus 4.4 (example: your own tarball or official DEBs) + - name: Install Lazarus 4.4 (ARM64) + run: | + sudo apt-get update + wget -q https://sourceforge.net/projects/lazarus/files/Lazarus%20Linux%20arm64%20DEB/Lazarus%204.4/fpc-laz_3.2.3-240813_arm64.deb -O /tmp/fpc-laz.deb + wget -q https://sourceforge.net/projects/lazarus/files/Lazarus%20Linux%20arm64%20DEB/Lazarus%204.4/fpc-src_3.2.3-240813_arm64.deb -O /tmp/fpc-src.deb + wget -q https://sourceforge.net/projects/lazarus/files/Lazarus%20Linux%20arm64%20DEB/Lazarus%204.4/lazarus-project_4.4.0-0_arm64.deb -O /tmp/lazarus.deb + sudo apt-get install -y /tmp/fpc-laz.deb /tmp/fpc-src.deb /tmp/lazarus.deb + # ensure Lazarus bin dir is on PATH, or export in this job: + echo "/usr/lib/lazarus/4.4" >> $GITHUB_PATH + + - name: Install Qt6 for ARM64 + run: | + sudo apt-get update + sudo apt-get install -y wget qt6-base-dev + LIBQT6PAS_VERSION="6.2.10" + wget -q "https://github.com/davidbannon/libqt6pas/releases/download/v${LIBQT6PAS_VERSION}/libqt6pas6_${LIBQT6PAS_VERSION}-1_arm64.deb" -O /tmp/libqt6pas6.deb + sudo dpkg -i /tmp/libqt6pas6.deb || sudo apt-get install -f -y + wget -q "https://github.com/davidbannon/libqt6pas/releases/download/v${LIBQT6PAS_VERSION}/libqt6pas6-dev_${LIBQT6PAS_VERSION}-1_arm64.deb" -O /tmp/libqt6pas6-dev.deb + sudo dpkg -i /tmp/libqt6pas6-dev.deb || sudo apt-get install -f -y + rm -f /tmp/libqt6pas6.deb /tmp/libqt6pas6-dev.deb + + - name: Build HeidiSQL (Linux ARM64 QT6) + run: | + echo Building with QT6 on ARM64 using Lazarus 4.4 + make build-qt6arm + + - name: Upload binaries QT6 ARM64 + uses: actions/upload-artifact@v4 + with: + name: ubuntu-24.04-arm-QT6 + path: out/qt6arm/heidisql + release: if: contains(github.ref_type, 'tag') env: @@ -137,7 +180,7 @@ jobs: name: Create GitHub Release runs-on: [ubuntu-latest] - needs: [build] + needs: [build, build-qt6arm] steps: - name: Checkout source code @@ -166,11 +209,18 @@ jobs: name: ubuntu-latest-QT6 path: out/qt6 + - name: Download Ubuntu QT6 ARM64 + uses: actions/download-artifact@v4 + with: + name: ubuntu-24.04-arm-QT6 + path: out/qt6arm + - name: List files run: | ls -alF out/gtk2 ls -alF out/qt5 ls -alF out/qt6 + ls -alF out/qt6arm - name: Install gettext run: sudo apt install -y gettext @@ -196,6 +246,9 @@ jobs: - name: Create release archives (Linux QT6) run: make tar-qt6 + - name: Create release archives (Linux QT6 ARM64) + run: make tar-qt6arm + - name: Generate Release Notes id: git-cliff uses: orhun/git-cliff-action@v4 diff --git a/.tx/config b/.tx/config index b9deca6c2..e6a64d65a 100644 --- a/.tx/config +++ b/.tx/config @@ -1,9 +1,9 @@ [main] -host = https://www.transifex.com +host = https://app.transifex.com [o:heidisql:p:heidisql:r:ui] -file_filter = extra/locale//LC_MESSAGES/default.po -source_file = extra/locale/en/LC_MESSAGES/default.po +file_filter = extra/locale/heidisql..po +source_file = extra/locale/heidisql.po source_lang = en type = PO minimum_perc = 20 diff --git a/Makefile b/Makefile index f75285ed5..f2a0bed47 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ BINWIN64 := ./out/win64/heidisql.exe BINGTK := ./out/gtk2/heidisql BINQT5 := ./out/qt5/heidisql BINQT6 := ./out/qt6/heidisql +BINQT6ARM := ./out/qt6arm/heidisql BINMACOS := ./out/macos/heidisql PEFLAGS := ./extra/peflags/peflags.exe @@ -87,6 +88,12 @@ build-qt6: @mkdir -p ./out/qt6 @mv -v $(BIN) $(BINQT6) +build-qt6arm: + @echo "=== Building QT6 ARM64" + $(LAZBUILD) $(OPTS) $(OPTSQT6) $(LPI) + @mkdir -p ./out/qt6arm + @mv -v $(BIN) $(BINQT6ARM) + run-qt6: build-qt6 @echo "=== Running QT6" @mkdir -p ./run/locale @@ -190,3 +197,15 @@ tar-qt6: cp -v out/qt6/heidisql tar chmod +x tar/heidisql cd tar && tar -zcvf ../dist/build-qt6-$(tag).tgz * + +tar-qt6arm: + @echo "=== Creating QT6 ARM64 archive" + rm -vrf tar + mkdir -p tar/locale dist + cp -v README.md LICENSE tar + cp -v res/deb-package-icon.png tar/heidisql.png + cp -v extra/locale/*.mo tar/locale + cp -v extra/ini/*.ini tar + cp -v out/qt6arm/heidisql tar + chmod +x tar/heidisql + cd tar && tar -zcvf ../dist/build-qt6-arm64-$(tag).tgz * diff --git a/extra/internationalization/libgettextlib.dll b/extra/internationalization/libgettextlib.dll new file mode 100644 index 000000000..6bc7525ba Binary files /dev/null and b/extra/internationalization/libgettextlib.dll differ diff --git a/extra/internationalization/libgettextsrc.dll b/extra/internationalization/libgettextsrc.dll new file mode 100644 index 000000000..0d5a59330 Binary files /dev/null and b/extra/internationalization/libgettextsrc.dll differ diff --git a/extra/internationalization/libiconv2.dll b/extra/internationalization/libiconv2.dll new file mode 100644 index 000000000..747073f1b Binary files /dev/null and b/extra/internationalization/libiconv2.dll differ diff --git a/extra/internationalization/libintl3.dll b/extra/internationalization/libintl3.dll new file mode 100644 index 000000000..ec11e6b19 Binary files /dev/null and b/extra/internationalization/libintl3.dll differ diff --git a/extra/internationalization/msgfmt.exe b/extra/internationalization/msgfmt.exe new file mode 100644 index 000000000..62ccb0ef6 Binary files /dev/null and b/extra/internationalization/msgfmt.exe differ diff --git a/extra/internationalization/tx.exe b/extra/internationalization/tx.exe new file mode 100644 index 000000000..87cf4d47d Binary files /dev/null and b/extra/internationalization/tx.exe differ diff --git a/extra/locale/heidisql.bg.mo b/extra/locale/heidisql.bg.mo index 3e1b83d79..6a1318627 100644 Binary files a/extra/locale/heidisql.bg.mo and b/extra/locale/heidisql.bg.mo differ diff --git a/extra/locale/heidisql.ca.mo b/extra/locale/heidisql.ca.mo index 40a0201cd..f871dac08 100644 Binary files a/extra/locale/heidisql.ca.mo and b/extra/locale/heidisql.ca.mo differ diff --git a/extra/locale/heidisql.cs.mo b/extra/locale/heidisql.cs.mo index 556804073..54104b7c9 100644 Binary files a/extra/locale/heidisql.cs.mo and b/extra/locale/heidisql.cs.mo differ diff --git a/extra/locale/heidisql.da.mo b/extra/locale/heidisql.da.mo index 7701e7078..db4f9e567 100644 Binary files a/extra/locale/heidisql.da.mo and b/extra/locale/heidisql.da.mo differ diff --git a/extra/locale/heidisql.de.mo b/extra/locale/heidisql.de.mo index 176a3bbac..ab42dea07 100644 Binary files a/extra/locale/heidisql.de.mo and b/extra/locale/heidisql.de.mo differ diff --git a/extra/locale/heidisql.el.mo b/extra/locale/heidisql.el.mo index d81b3b356..67a951c3e 100644 Binary files a/extra/locale/heidisql.el.mo and b/extra/locale/heidisql.el.mo differ diff --git a/extra/locale/heidisql.es.mo b/extra/locale/heidisql.es.mo index 1883d7f99..dd8a92367 100644 Binary files a/extra/locale/heidisql.es.mo and b/extra/locale/heidisql.es.mo differ diff --git a/extra/locale/heidisql.eu.mo b/extra/locale/heidisql.eu.mo index 2285088c9..2e671be77 100644 Binary files a/extra/locale/heidisql.eu.mo and b/extra/locale/heidisql.eu.mo differ diff --git a/extra/locale/heidisql.fi.mo b/extra/locale/heidisql.fi.mo index a699ea66a..23d702391 100644 Binary files a/extra/locale/heidisql.fi.mo and b/extra/locale/heidisql.fi.mo differ diff --git a/extra/locale/heidisql.fr.mo b/extra/locale/heidisql.fr.mo index e722289cf..6002aeccc 100644 Binary files a/extra/locale/heidisql.fr.mo and b/extra/locale/heidisql.fr.mo differ diff --git a/extra/locale/heidisql.he.mo b/extra/locale/heidisql.he.mo index 393baebc0..b0fc7e96f 100644 Binary files a/extra/locale/heidisql.he.mo and b/extra/locale/heidisql.he.mo differ diff --git a/extra/locale/heidisql.hu.mo b/extra/locale/heidisql.hu.mo index f2846f944..eb209e50e 100644 Binary files a/extra/locale/heidisql.hu.mo and b/extra/locale/heidisql.hu.mo differ diff --git a/extra/locale/heidisql.id.mo b/extra/locale/heidisql.id.mo index 32c013a61..43d8981d6 100644 Binary files a/extra/locale/heidisql.id.mo and b/extra/locale/heidisql.id.mo differ diff --git a/extra/locale/heidisql.it.mo b/extra/locale/heidisql.it.mo index 2e2f4f655..56f977abc 100644 Binary files a/extra/locale/heidisql.it.mo and b/extra/locale/heidisql.it.mo differ diff --git a/extra/locale/heidisql.ja.mo b/extra/locale/heidisql.ja.mo index ea12ca62c..2b653b564 100644 Binary files a/extra/locale/heidisql.ja.mo and b/extra/locale/heidisql.ja.mo differ diff --git a/extra/locale/heidisql.ko.mo b/extra/locale/heidisql.ko.mo index c13979588..f3cbc17cc 100644 Binary files a/extra/locale/heidisql.ko.mo and b/extra/locale/heidisql.ko.mo differ diff --git a/extra/locale/heidisql.mo b/extra/locale/heidisql.mo new file mode 100644 index 000000000..a00df8c91 Binary files /dev/null and b/extra/locale/heidisql.mo differ diff --git a/extra/locale/heidisql.nl.mo b/extra/locale/heidisql.nl.mo index 11ae7275c..77c9cac29 100644 Binary files a/extra/locale/heidisql.nl.mo and b/extra/locale/heidisql.nl.mo differ diff --git a/extra/locale/heidisql.no.mo b/extra/locale/heidisql.no.mo index e2e38a25e..9a1fd58dd 100644 Binary files a/extra/locale/heidisql.no.mo and b/extra/locale/heidisql.no.mo differ diff --git a/extra/locale/heidisql.pl.mo b/extra/locale/heidisql.pl.mo index 5d43932b0..1fe4e9867 100644 Binary files a/extra/locale/heidisql.pl.mo and b/extra/locale/heidisql.pl.mo differ diff --git a/extra/locale/heidisql.po b/extra/locale/heidisql.po index 274efe278..f281378ec 100644 --- a/extra/locale/heidisql.po +++ b/extra/locale/heidisql.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: HeidiSQL\n" "POT-Creation-Date: 2012-11-05 21:40\n" -"PO-Revision-Date: 2026-06-03 17:02+0200\n" +"PO-Revision-Date: 2026-06-14 12:08+0200\n" "Last-Translator: Ansgar Becker \n" "Language-Team: English (http://www.transifex.com/projects/p/heidisql/language/en/)\n" "Language: en\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Poedit 3.8\n" +"X-Generator: Poedit 3.9\n" #. AboutBox..Caption #: about.dfm:5 @@ -2803,17 +2803,17 @@ msgstr "Close tab on doubleclick" msgid "Close tab on middleclick" msgstr "Close tab on middleclick" -msgid "Grayscale inactive tab icons" -msgstr "Grayscale inactive tab icons" +msgid "Inactive tab icons" +msgstr "Inactive tab icons" -msgid "Color icons on all tabs" -msgstr "Color icons on all tabs" +msgid "Normal icons on all tabs" +msgstr "Normal icons on all tabs" -msgid "Grayscale icons on inactive query tabs only" -msgstr "Grayscale icons on inactive query tabs only" +msgid "Disabled icons on inactive query tabs only" +msgstr "Disabled icons on inactive query tabs only" -msgid "Grayscale icons on every inactive tab" -msgstr "Grayscale icons on every inactive tab" +msgid "Disabled icons on every inactive tab" +msgstr "Disabled icons on every inactive tab" msgid "Tabs in multiple lines" msgstr "Tabs in multiple lines" @@ -6854,3 +6854,9 @@ msgstr "Edit value without foreign key lookup" msgid "Only focused column (%s)" msgstr "Only focused column (%s)" + +msgid "Empty password after plugin change!" +msgstr "Empty password after plugin change!" + +msgid "You changed the authentication plugin. This will reset the password, so please specify a new one." +msgstr "You changed the authentication plugin. This will reset the password, so please specify a new one." diff --git a/extra/locale/heidisql.pt.mo b/extra/locale/heidisql.pt.mo index 95ab1ed6c..96ef759ca 100644 Binary files a/extra/locale/heidisql.pt.mo and b/extra/locale/heidisql.pt.mo differ diff --git a/extra/locale/heidisql.pt_BR.mo b/extra/locale/heidisql.pt_BR.mo index d3017d69e..8a8b87028 100644 Binary files a/extra/locale/heidisql.pt_BR.mo and b/extra/locale/heidisql.pt_BR.mo differ diff --git a/extra/locale/heidisql.ro.mo b/extra/locale/heidisql.ro.mo index a1aad45eb..bf53d8680 100644 Binary files a/extra/locale/heidisql.ro.mo and b/extra/locale/heidisql.ro.mo differ diff --git a/extra/locale/heidisql.ru.mo b/extra/locale/heidisql.ru.mo index f18537045..5e89c07c4 100644 Binary files a/extra/locale/heidisql.ru.mo and b/extra/locale/heidisql.ru.mo differ diff --git a/extra/locale/heidisql.sk.mo b/extra/locale/heidisql.sk.mo index 2f417ec00..8ce15d053 100644 Binary files a/extra/locale/heidisql.sk.mo and b/extra/locale/heidisql.sk.mo differ diff --git a/extra/locale/heidisql.sl.mo b/extra/locale/heidisql.sl.mo index f289433b5..ee4f73d7b 100644 Binary files a/extra/locale/heidisql.sl.mo and b/extra/locale/heidisql.sl.mo differ diff --git a/extra/locale/heidisql.sv.mo b/extra/locale/heidisql.sv.mo index 5ad1c7087..ab924a3a4 100644 Binary files a/extra/locale/heidisql.sv.mo and b/extra/locale/heidisql.sv.mo differ diff --git a/extra/locale/heidisql.ta.mo b/extra/locale/heidisql.ta.mo index 4c9e4f239..4d8d6d8b6 100644 Binary files a/extra/locale/heidisql.ta.mo and b/extra/locale/heidisql.ta.mo differ diff --git a/extra/locale/heidisql.tr.mo b/extra/locale/heidisql.tr.mo index 9fceb509c..7f32412b3 100644 Binary files a/extra/locale/heidisql.tr.mo and b/extra/locale/heidisql.tr.mo differ diff --git a/extra/locale/heidisql.uk.mo b/extra/locale/heidisql.uk.mo index c1592c8c3..e4487e16b 100644 Binary files a/extra/locale/heidisql.uk.mo and b/extra/locale/heidisql.uk.mo differ diff --git a/extra/locale/heidisql.vi.mo b/extra/locale/heidisql.vi.mo index 878d07fe7..9b580b034 100644 Binary files a/extra/locale/heidisql.vi.mo and b/extra/locale/heidisql.vi.mo differ diff --git a/extra/locale/heidisql.zh.mo b/extra/locale/heidisql.zh.mo index 784e04b68..66ae0fd4a 100644 Binary files a/extra/locale/heidisql.zh.mo and b/extra/locale/heidisql.zh.mo differ diff --git a/extra/locale/heidisql.zh_CN.mo b/extra/locale/heidisql.zh_CN.mo index 4aa79cb53..4111c380b 100644 Binary files a/extra/locale/heidisql.zh_CN.mo and b/extra/locale/heidisql.zh_CN.mo differ diff --git a/extra/locale/heidisql.zh_TW.mo b/extra/locale/heidisql.zh_TW.mo index 16c769b17..005a31920 100644 Binary files a/extra/locale/heidisql.zh_TW.mo and b/extra/locale/heidisql.zh_TW.mo differ diff --git a/heidisql.lpi b/heidisql.lpi index a02bd963a..d7451cfea 100644 --- a/heidisql.lpi +++ b/heidisql.lpi @@ -25,7 +25,7 @@ - + diff --git a/package-skeleton/usr/share/applications/heidisql.desktop b/package-skeleton/usr/share/applications/heidisql.desktop index d817dd68b..05d8f0139 100644 --- a/package-skeleton/usr/share/applications/heidisql.desktop +++ b/package-skeleton/usr/share/applications/heidisql.desktop @@ -1,12 +1,15 @@ [Desktop Entry] +Type=Application Version=1.0 -Encoding=UTF-8 + Name=HeidiSQL -Exec=heidisql %f +Comment=A lightweight database management interface +Icon=heidisql + +Exec=heidisql %F TryExec=heidisql -Comment=A lightweight interface to MySQL Terminal=false -Categories=Development; -Icon=heidisql -Type=Application +StartupWMClass=heidisql +Categories=Database;Development; +Keywords=database;sql;mysql;mariadb;postgresql;mssql;sqlite; diff --git a/source/apphelpers.pas b/source/apphelpers.pas index c97f390ff..66656326b 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -7,7 +7,7 @@ interface uses Classes, SysUtils, Generics.Collections, Controls, RegExpr, Math, FileUtil, StrUtils, Graphics, GraphUtil, LCLIntf, Forms, Clipbrd, Process, ActnList, Menus, Dialogs, - Character, DateUtils, laz.VirtualTrees, SynEdit, SynCompletion, fphttpclient, + Character, DateUtils, laz.VirtualTrees, SynEdit, SynCompletion, fphttpclient, IntfGraphics, FPImage, {$IFDEF WINDOWS} Windows, Registry, uDarkStyleParams, {$ENDIF} DelphiCompat, dbconnection, dbstructures, jsonregistry, lazaruscompat, fpjson, SynEditKeyCmds, LazFileUtils, gettext, LazUTF8, IniFiles, GraphType, Sockets, Contnrs @@ -435,6 +435,7 @@ TAppSettings = class(TObject) // This returns a stable, lowercase name "heidisql", used for configuration files and translations function GetApplicationName: String; procedure CopyImageList(SourceList, TargetList: TImageList; AppendDisabled: Boolean); + procedure LightenBitmap(Bmp: Graphics.TBitmap; Amount: Byte); var AppSettings: TAppSettings; @@ -530,16 +531,17 @@ function Explode(Separator, Text: String): TStringList; } function StrEllipsis(const S: String; MaxLen: Integer; FromLeft: Boolean=True): String; begin + // Truncate on UTF-8 codepoint boundaries, not raw bytes. A byte-wise cut (SetLength/Copy) + // can split a multi-byte character and produce invalid UTF-8. On the Cocoa widgetset such a + // string converts to a nil NSString, which crashes -[NSMenuItem initWithTitle:] when the + // result is used as a menu caption (e.g. quick filter items). Result := S; - if Length(Result) <= MaxLen then + if UTF8Length(Result) <= MaxLen then Exit; - if FromLeft then begin - SetLength(Result, MaxLen); - Result := Result + '…'; - end else begin - Result := Copy(Result, Length(Result)-MaxLen, Length(Result)); - Result := '…' + Result; - end; + if FromLeft then + Result := UTF8Copy(Result, 1, MaxLen) + '…' + else + Result := '…' + UTF8Copy(Result, UTF8Length(Result) - MaxLen + 1, MaxLen); end; @@ -3039,7 +3041,8 @@ procedure CopyImageList(SourceList, TargetList: TImageList; AppendDisabled: Bool for ResIdx:=Low(Resolutions) to High(Resolutions) do begin ResWidth := Resolutions[ResIdx]; TempBitmap := Graphics.TBitmap.Create; - SourceList.Resolution[ResWidth].GetBitmap(i, TempBitmap, gdeDisabled); + SourceList.Resolution[ResWidth].GetBitmap(i, TempBitmap, gdeNormal); + LightenBitmap(TempBitmap, 140); TempBitmapList[ResIdx] := TempBitmap; end; TargetList.AddMultipleResolutions(TempBitmapList); @@ -3047,6 +3050,38 @@ procedure CopyImageList(SourceList, TargetList: TImageList; AppendDisabled: Bool end; end; +procedure LightenBitmap(Bmp: Graphics.TBitmap; Amount: Byte); +var + x, y: Integer; + Img: TLazIntfImage; + C: TFPColor; +begin + // This is used for creating "disabled" looking images. + // Was grayscaled before (ImageList.Draw with gdeDisabled effect). But now this is + // also used for unseen database tree objects, this mechanism looks better. + // https://www.heidisql.com/forum.php?t=45161 + Img := TLazIntfImage.Create(0, 0); + try + Img.LoadFromBitmap(Bmp.Handle, Bmp.MaskHandle); + + for y := 0 to Img.Height - 1 do + for x := 0 to Img.Width - 1 do + begin + C := Img.Colors[x, y]; + + C.red := C.red + ((65535 - C.red) * Amount) div 255; + C.green := C.green + ((65535 - C.green) * Amount) div 255; + C.blue := C.blue + ((65535 - C.blue) * Amount) div 255; + + Img.Colors[x, y] := C; + end; + + Bmp.LoadFromIntfImage(Img); + finally + Img.Free; + end; +end; + { Threading stuff } diff --git a/source/column_selection.pas b/source/column_selection.pas index 34fe64ea5..1bbdecb91 100644 --- a/source/column_selection.pas +++ b/source/column_selection.pas @@ -118,16 +118,16 @@ procedure TfrmColumnSelection.chkSelectAllClick(Sender: TObject); cb: TCheckBox; i: Integer; begin - // Avoid executing when checkbox was toggled by code (see proc below) + // Avoid executing when Checked or State property was modified by code. Happens in OnClickCheck handler of TCheckListBox. cb := Sender as TCheckBox; - if cb.Focused then begin - chklistColumns.CheckAll(cb.State); - for i:=0 to chklistColumns.Items.Count-1 do begin - if (FCheckedColumns.IndexOf(chklistColumns.Items[i]) = -1) and (cb.State = cbChecked) then - FCheckedColumns.Add(chklistColumns.Items[i]); - if (FCheckedColumns.IndexOf(chklistColumns.Items[i]) > -1) and (cb.State = cbUnchecked) then - FCheckedColumns.Delete(FCheckedColumns.IndexOf(chklistColumns.Items[i])); - end; + if cb.Tag = SUSPEND_ONCLICK then + Exit; + chklistColumns.CheckAll(cb.State); + for i:=0 to chklistColumns.Items.Count-1 do begin + if (FCheckedColumns.IndexOf(chklistColumns.Items[i]) = -1) and (cb.State = cbChecked) then + FCheckedColumns.Add(chklistColumns.Items[i]); + if (FCheckedColumns.IndexOf(chklistColumns.Items[i]) > -1) and (cb.State = cbUnchecked) then + FCheckedColumns.Delete(FCheckedColumns.IndexOf(chklistColumns.Items[i])); end; end; @@ -175,12 +175,14 @@ procedure TfrmColumnSelection.chklistColumnsClickCheck(Sender: TObject); else AllSelected := False; end; + chkSelectAll.Tag := SUSPEND_ONCLICK; if NoneSelected then chkSelectAll.State := cbUnchecked else if AllSelected then chkSelectAll.State := cbChecked else chkSelectAll.State := cbGrayed; + chkSelectAll.Tag := 0; end; @@ -191,19 +193,20 @@ procedure TfrmColumnSelection.PopulateList(Sender: TObject); var i: Integer; Col: String; + TempList: TStringList; begin // Setting Sorted to false doesn't resort anything in the list. // So we have to add all items again in original order - chklistColumns.Sorted := chkSort.Checked; - // Add all fieldnames again - chklistColumns.Items.BeginUpdate; - chklistColumns.Clear; + TempList := TStringList.Create; + TempList.Sorted := chkSort.Checked; for i:=0 to Mainform.SelectedTableColumns.Count-1 do begin Col := Mainform.SelectedTableColumns[i].Name; if IsEmpty(editFilter.Text) or (Pos(LowerCase(editFilter.Text), LowerCase(Col)) > 0) then - chklistColumns.Items.Add(Col); + TempList.Add(Col); end; - chklistColumns.Items.EndUpdate; + chklistColumns.Clear; + chklistColumns.Items.AddStrings(TempList); + TempList.Free; // check those which remembered as checked for i:=0 to chklistColumns.Items.Count-1 do begin diff --git a/source/const.inc b/source/const.inc index b31cbbe06..4fe6822b4 100644 --- a/source/const.inc +++ b/source/const.inc @@ -89,6 +89,8 @@ const // Modification indicator for TControl.Tag MODIFIEDFLAG = 10; + // Suspend click event handling tag for checkboxes + SUSPEND_ONCLICK = 20; SUnhandledNodeIndex = 'Unhandled tree node index'; MSG_NOGRIDEDITING = 'Selected columns don''t contain a sufficient set of key columns to allow editing. Please select primary or unique key columns, or just all columns.'; diff --git a/source/dbconnection.pas b/source/dbconnection.pas index 4182a4679..5d4508e2b 100644 --- a/source/dbconnection.pas +++ b/source/dbconnection.pas @@ -1119,7 +1119,7 @@ procedure TSecureShellCmd.Connect; if FConnection.Parameters.SSHPrivateKey <> '' then SshCmd := SshCmd + ' -i "' + FConnection.Parameters.SSHPrivateKey + '"'; // OpenSSH options must be placed before the destination host. - SshCmd := SshCmd + ' -o StrictHostKeyChecking=no -o IgnoreUnknown=WarnWeakCrypto -o WarnWeakCrypto=no-pq-kex -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -o ServerAliveCountMax=3'; + SshCmd := SshCmd + ' -o StrictHostKeyChecking=no -o IgnoreUnknown=WarnWeakCrypto -o WarnWeakCrypto=no-pq-kex -o ExitOnForwardFailure=yes -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -o LogLevel=error'; SshCmd := SshCmd + ' -N -L ' + IntToStr(FConnection.Parameters.SSHLocalPort) + ':' + FConnection.Parameters.Hostname + ':' + IntToStr(FConnection.Parameters.Port) + ' ' + TargetHost; end; @@ -5921,6 +5921,10 @@ function TSQLiteConnection.GetTableColumns(Table: TDBObject): TTableColumnList; Col.AllowNull := ColQuery.Col('notnull') <> '1'; Col.DefaultType := cdtNothing; Col.DefaultText := ''; + if not ColQuery.Col('dflt_value').IsEmpty then begin + Col.DefaultType := cdtText; + Col.DefaultText := ColQuery.Col('dflt_value').DeQuotedString(FStringQuoteChar); + end; Col.OnUpdateType := cdtNothing; Col.OnUpdateText := ''; case StrToIntDef(ColQuery.Col('hidden'), 0) of diff --git a/source/dbstructures.mysql.pas b/source/dbstructures.mysql.pas index 19df33a5c..61fd47b4c 100644 --- a/source/dbstructures.mysql.pas +++ b/source/dbstructures.mysql.pas @@ -3245,9 +3245,10 @@ procedure TMySQLLib.AssignProcedures; function TMySqlProvider.GetSql(AId: TQueryId): string; var - IsMariaDB: Boolean; + IsMariaDB, IsMySQL: Boolean; begin IsMariaDB := ServerVersion >= 100000; + IsMySQL := not IsMariaDB; case AId of qDatabaseDrop: Result := 'DROP DATABASE %s'; qEmptyTable: Result := 'TRUNCATE %s'; @@ -3268,7 +3269,11 @@ function TMySqlProvider.GetSql(AId: TQueryId): string; 'SHOW /*!50002 GLOBAL */ STATUS LIKE ''Com\_%''' ); qSessionVariables: Result := 'SHOW VARIABLES'; - qGlobalVariables: Result := 'SHOW GLOBAL VARIABLES'; + qGlobalVariables: Result := IfThen( + FServerVersion >= 40000, + 'SHOW GLOBAL VARIABLES', + '' + ); qISSchemaCol: Result := '%s_SCHEMA'; qUSEQuery: Result := 'USE %s'; qKillQuery: Result := IfThen( @@ -3361,12 +3366,12 @@ function TMySqlProvider.GetSql(AId: TQueryId): string; ); qShowFunctionStatus: Result := IfThen( (FServerVersion >= 50000) and (FNetType <> ntMySQL_ProxySQLAdmin), - 'SHOW FUNCTION STATUS WHERE Db = %s', + 'SHOW FUNCTION STATUS WHERE Db = LOWER(%s)', '' ); qShowProcedureStatus: Result := IfThen( (FServerVersion >= 50000) and (FNetType <> ntMySQL_ProxySQLAdmin), - 'SHOW PROCEDURE STATUS WHERE Db = %s', + 'SHOW PROCEDURE STATUS WHERE Db = LOWER(%s)', '' ); qShowTriggers: Result := IfThen( @@ -3405,24 +3410,20 @@ function TMySqlProvider.GetSql(AId: TQueryId): string; qGrantRole: Result := 'GRANT %s TO %s%s'; qRevokeRole: Result := 'REVOKE %s FROM %s'; qSetDefaultRole: Result := 'SET DEFAULT ROLE %s FOR %s'; - qIndexVisible: Result := IfThen( - FServerVersion >= 100600, // mariadb - 'NOT IGNORED', - IfThen( - FServerVersion >= 80000, // mysql - 'VISIBLE', - '' - ) - ); - qIndexInvisible: Result := IfThen( - FServerVersion >= 100600, // mariadb - 'IGNORED', - IfThen( - FServerVersion >= 80000, // mysql - 'INVISIBLE', - '' - ) - ); + qIndexVisible: + if IsMariaDB and (FServerVersion >= 100600) then + Result := 'NOT IGNORED' + else if IsMySQL and (FServerVersion >= 80000) then + Result := 'VISIBLE' + else + Result := ''; + qIndexInvisible: + if IsMariaDB and (FServerVersion >= 100600) then + Result := 'IGNORED' + else if IsMySQL and (FServerVersion >= 80000) then + Result := 'INVISIBLE' + else + Result := ''; qGetAuthPlugins: Result := IfThen( (FServerVersion >= 50100) or IsMariaDB, // mysql 5.1+ and all mariadb versions 'SELECT PLUGIN_NAME FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_TYPE=''AUTHENTICATION'' AND PLUGIN_STATUS=''ACTIVE''', diff --git a/source/exportgrid.pas b/source/exportgrid.pas index 80f88f60a..db314500c 100644 --- a/source/exportgrid.pas +++ b/source/exportgrid.pas @@ -228,7 +228,10 @@ procedure TfrmExportGrid.FormShow(Sender: TObject); chkFocusedColumnOnly.OnClick := CalcSize; CalcSize(Sender); // Show name of focused column - FocusedCol := IfThen(Grid.FocusedColumn > NoColumn, Grid.Header.Columns[Grid.FocusedColumn].Text, ''); + if Grid.FocusedColumn > -1 then + FocusedCol := Grid.Header.Columns[Grid.FocusedColumn].Text + else + FocusedCol := ''; chkFocusedColumnOnly.Caption := f_('Only focused column (%s)', [FocusedCol]); chkFocusedColumnOnly.Enabled := not FocusedCol.IsEmpty; end; diff --git a/source/grideditlinks.pas b/source/grideditlinks.pas index 15f24a9fa..46aec8ee5 100644 --- a/source/grideditlinks.pas +++ b/source/grideditlinks.pas @@ -1878,7 +1878,7 @@ procedure TDataTypeEditorLink.DoTreeSelectPaintText(Sender: TBaseVirtualTree; begin // Give datatype column specific color, as set in preferences case Sender.GetNodeLevel(Node) of - 0: TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; + 0: TargetCanvas.Font.Bold := True; 1: if not (vsSelected in Node.States) then TargetCanvas.Font.Color := AppColorSchemes.First.GridTextColors[TDBDatatypeCategoryIndex(Node.Parent.Index)]; end; diff --git a/source/main.pas b/source/main.pas index 6b2b54b7a..92e1e4bc5 100644 --- a/source/main.pas +++ b/source/main.pas @@ -5090,6 +5090,7 @@ procedure TMainForm.actQueryTableExecute(Sender: TObject); for Obj in Objects do begin Tab := GetOrCreateEmptyQueryTab(True); Tab.Memo.Text := Conn.ApplyLimitClause('SELECT', '* FROM '+Obj.QuotedName, AppSettings.ReadInt(asDatagridRowsPerStep), 0); + SetTabCaption(Tab.TabSheet.TabIndex, Obj.Name); actExecuteQueryExecute(Sender); end; end; @@ -6332,6 +6333,10 @@ procedure TMainForm.PageControlMainChange(Sender: TObject); Exit; tab := PageControlMain.ActivePage; + // ActivePage can be nil while tabs are being hidden during session teardown (disconnect), + // which would raise an access violation below on tab.PageIndex. + if tab = nil then + Exit; // Query helpers need a hit here, since RefreshHelperNode now only does its update on the active tab // See https://www.heidisql.com/forum.php?t=37961 RefreshHelperNode(TQueryTab.HelperNodeColumns); @@ -9572,6 +9577,7 @@ procedure TMainForm.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: Boolean; var ImageIndex: Integer); var DBObj: PDBObject; + CountOriginals: Integer; begin if Column > 0 then Exit; @@ -9579,8 +9585,27 @@ procedure TMainForm.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: if not Assigned(DBObj) then Exit; case Kind of - ikNormal, ikSelected: - ImageIndex := DBObj.ImageIndex; + ikNormal, ikSelected: begin + ImageIndex := DBObj.ImageIndex; + Ghosted := (DBObj.NodeType = lntNone) and (not DBObj.Connection.Active); + Ghosted := Ghosted or ((DBObj.NodeType = lntDB) + and (not DBObj.Connection.DbObjectsCached(DBObj.Database)) + ); + Ghosted := Ghosted or ((DBObj.NodeType = lntGroup) + and Sender.ChildrenInitialized[Node] + and (Sender.ChildCount[Node] = 0) + ); + Ghosted := Ghosted or ((DBObj.NodeType in [lntTable..lntEvent]) + and (not DBObj.WasSelected) + ); + // Unlike in the Delphi version, imagelists have painting issues with Ghosted marker enabled. + // So instead we use the self-created "disabled" version of the same icon in the top of the imagelist. + if Ghosted then begin + CountOriginals := DBtree.Images.Count div 2; + Inc(ImageIndex, CountOriginals); + Ghosted := False; + end; + end; ikOverlay: ImageIndex := DBObj.OverlayImageIndex; end; @@ -10053,49 +10078,34 @@ procedure TMainForm.DBtreePaintText(Sender: TBaseVirtualTree; const var DBObj: PDBObject; WalkNode: PVirtualNode; - Ghosted: Boolean; begin // Grey out non-current connection nodes, and rather unimportant "Size" column DBObj := Sender.GetNodeData(Node); - if DBObj.Connection <> ActiveConnection then begin + if DBObj.Connection <> ActiveConnection then + TargetCanvas.Font.Color := clGrayText + else if (Column = 1) and (DBObj.NodeType in [lntTable..lntEvent]) then TargetCanvas.Font.Color := clGrayText; - Exit; - end; // Set bold text if painted node is in focused path - WalkNode := Sender.FocusedNode; - while Assigned(WalkNode) do begin - if WalkNode = Node then begin - TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; - Break; - end; - try - // This crashes in some situations, which I could never reproduce. - // See uploaded crash reports and issue #1270. - WalkNode := Sender.NodeParent[WalkNode]; - except - on E:EAccessViolation do begin - LogSQL('DBtreePaintText, NodeParent: '+E.Message, lcError); + if (Column = DBtree.Header.MainColumn) then begin + WalkNode := Sender.FocusedNode; + while Assigned(WalkNode) do begin + if WalkNode = Node then begin + TargetCanvas.Font.Bold := True; Break; end; + try + // This crashes in some situations, which I could never reproduce. + // See uploaded crash reports and issue #1270. + WalkNode := Sender.NodeParent[WalkNode]; + except + on E:EAccessViolation do begin + LogSQL('DBtreePaintText, NodeParent: '+E.Message, lcError); + Break; + end; + end; end; end; - - // Moved from OnGetImageIndex, where the icon used to be painted lighter if the node was yet unseen - Ghosted := (DBObj.NodeType = lntNone) and (not DBObj.Connection.Active); - Ghosted := Ghosted or ((DBObj.NodeType = lntDB) - and (not DBObj.Connection.DbObjectsCached(DBObj.Database)) - ); - Ghosted := Ghosted or ((DBObj.NodeType = lntGroup) - and Sender.ChildrenInitialized[Node] - and (Sender.ChildCount[Node] = 0) - ); - Ghosted := Ghosted or ((DBObj.NodeType in [lntTable..lntEvent]) - and (not DBObj.WasSelected) - ); - if Ghosted then - TargetCanvas.Font.Color := clGrayText; - end; @@ -10538,7 +10548,7 @@ procedure TMainForm.AnyGridPaintText(Sender: TBaseVirtualTree; const TargetCanva // Make primary key columns bold if r.ColIsPrimaryKeyPart(ResultCol) then - TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; + TargetCanvas.Font.Bold := True; // Do not apply any color on a selected, highlighted cell to keep readability if (vsSelected in Node.States) and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn) then @@ -11625,14 +11635,16 @@ procedure TMainForm.HostListBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: Variables.Next; end; Variables.Free; - Variables := Conn.GetResults(Conn.SqlProvider.GetSql(qGlobalVariables)); - while not Variables.Eof do begin - FVariableNames.Add(Variables.Col(0)); - FGlobalVars.Values[Variables.Col(0)] := Variables.Col(1); - FGlobalVars.Values[Variables.Col(0)] := IfThen(Variables.IsNull(1), TEXT_NULL, Variables.Col(1)); - Variables.Next; + if Conn.SqlProvider.Has(qGlobalVariables) then begin + Variables := Conn.GetResults(Conn.SqlProvider.GetSql(qGlobalVariables)); + while not Variables.Eof do begin + FVariableNames.Add(Variables.Col(0)); + FGlobalVars.Values[Variables.Col(0)] := Variables.Col(1); + FGlobalVars.Values[Variables.Col(0)] := IfThen(Variables.IsNull(1), TEXT_NULL, Variables.Col(1)); + Variables.Next; + end; + Variables.Free; end; - Variables.Free; vt.RootNodeCount := FVariableNames.Count; end else if vt = ListStatus then begin Results := Conn.GetResults(Conn.SqlProvider.GetSql(qGlobalStatus)); @@ -14145,8 +14157,10 @@ procedure TMainForm.treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const Ta and (Column=1) and (Node.Parent.Index=TQueryTab.HelperNodeBinding) then begin Tab := QueryTabs.TabByControl(Sender); - if StrLen(PChar(Tab.ListBindParams.Items[Node.Index].Value)) = 0 then - TargetCanvas.Font.Style := [fsItalic]+[fsUnderline]; + if StrLen(PChar(Tab.ListBindParams.Items[Node.Index].Value)) = 0 then begin + TargetCanvas.Font.Italic := True; + TargetCanvas.Font.Underline := True; + end; TargetCanvas.Font.Color := GetThemeColor(clGrayText); diff --git a/source/preferences.lfm b/source/preferences.lfm index 9dbcdee74..8106fb637 100644 --- a/source/preferences.lfm +++ b/source/preferences.lfm @@ -1892,9 +1892,9 @@ object frmPreferences: TfrmPreferences Left = 5 Height = 15 Top = 104 - Width = 145 + Width = 92 BorderSpacing.Around = 5 - Caption = 'Grayscale inactive tab icons' + Caption = 'Inactive tab icons' end object lblReformatter: TLabel AnchorSideLeft.Control = tabFiles @@ -1968,9 +1968,9 @@ object frmPreferences: TfrmPreferences BorderSpacing.Around = 5 ItemHeight = 15 Items.Strings = ( - 'Color icons on all tabs' - 'Grayscale icons on inactive query tabs only' - 'Grayscale icons on every inactive tab' + 'Normal icons on all tabs' + 'Disabled icons on inactive query tabs only' + 'Disabled icons on every inactive tab' ) Style = csDropDownList TabOrder = 4 diff --git a/source/table_editor.pas b/source/table_editor.pas index 26ceae95a..1786ba590 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -1625,7 +1625,7 @@ procedure TfrmTableEditor.listColumnsPaintText(Sender: TBaseVirtualTree; // Bold font for primary key columns for i:=0 to FKeys.Count-1 do begin if FKeys[i].IsPrimary and (FKeys[i].Columns.IndexOf(Col.Name) > -1) then begin - TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; + TargetCanvas.Font.Bold := True; break; end; end; diff --git a/source/usermanager.lfm b/source/usermanager.lfm index c21d8a8c2..7dcc6e733 100644 --- a/source/usermanager.lfm +++ b/source/usermanager.lfm @@ -177,7 +177,7 @@ object UserManagerForm: TUserManagerForm TabOrder = 1 object PageControlSettings: TPageControl Left = 0 - Height = 201 + Height = 221 Top = 0 Width = 385 ActivePage = tabCredentials @@ -188,7 +188,7 @@ object UserManagerForm: TUserManagerForm object tabCredentials: TTabSheet AutoSize = True Caption = 'Credentials' - ClientHeight = 173 + ClientHeight = 193 ClientWidth = 377 object lblUsername: TLabel AnchorSideLeft.Control = tabCredentials @@ -231,22 +231,24 @@ object UserManagerForm: TUserManagerForm AnchorSideTop.Side = asrCenter Left = 5 Height = 15 - Top = 93 + Top = 113 Width = 92 BorderSpacing.Around = 5 Caption = 'Repeat password:' FocusControl = editRepeatPassword end object editRepeatPassword: TEdit - AnchorSideTop.Control = editPassword + AnchorSideLeft.Control = tabCredentials + AnchorSideTop.Control = lblPasswordHint AnchorSideTop.Side = asrBottom AnchorSideRight.Control = tabCredentials AnchorSideRight.Side = asrBottom Left = 150 Height = 23 - Top = 89 + Top = 109 Width = 222 Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 145 BorderSpacing.Around = 5 EchoMode = emPassword PasswordChar = '*' @@ -254,6 +256,7 @@ object UserManagerForm: TUserManagerForm OnChange = Modification end object editPassword: TEditButton + AnchorSideLeft.Control = tabCredentials AnchorSideTop.Control = editFromHost AnchorSideTop.Side = asrBottom AnchorSideRight.Control = tabCredentials @@ -263,6 +266,7 @@ object UserManagerForm: TUserManagerForm Top = 61 Width = 222 Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 145 BorderSpacing.Around = 5 ButtonWidth = 23 EchoMode = emPassword @@ -276,6 +280,7 @@ object UserManagerForm: TUserManagerForm OnChange = editPasswordChange end object editFromHost: TEditButton + AnchorSideLeft.Control = tabCredentials AnchorSideTop.Control = editUsername AnchorSideTop.Side = asrBottom AnchorSideRight.Control = tabCredentials @@ -285,6 +290,7 @@ object UserManagerForm: TUserManagerForm Top = 33 Width = 222 Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 145 BorderSpacing.Around = 5 ButtonWidth = 23 Images = MainForm.ImageListMain @@ -297,6 +303,7 @@ object UserManagerForm: TUserManagerForm OnChange = Modification end object editUsername: TEdit + AnchorSideLeft.Control = tabCredentials AnchorSideTop.Control = tabCredentials AnchorSideRight.Control = tabCredentials AnchorSideRight.Side = asrBottom @@ -305,20 +312,23 @@ object UserManagerForm: TUserManagerForm Top = 5 Width = 222 Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 145 BorderSpacing.Around = 5 TabOrder = 0 OnChange = Modification end object comboDefaultRole: TComboBox + AnchorSideLeft.Control = tabCredentials AnchorSideTop.Control = comboPlugins AnchorSideTop.Side = asrBottom AnchorSideRight.Control = tabCredentials AnchorSideRight.Side = asrBottom Left = 150 Height = 23 - Top = 145 + Top = 165 Width = 222 Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 145 BorderSpacing.Around = 5 ItemHeight = 15 Style = csDropDownList @@ -331,7 +341,7 @@ object UserManagerForm: TUserManagerForm AnchorSideTop.Side = asrCenter Left = 5 Height = 15 - Top = 149 + Top = 169 Width = 64 BorderSpacing.Around = 5 Caption = 'Default role:' @@ -342,32 +352,50 @@ object UserManagerForm: TUserManagerForm AnchorSideTop.Side = asrCenter Left = 5 Height = 15 - Top = 121 + Top = 141 Width = 37 BorderSpacing.Around = 5 Caption = 'Plugin:' end object comboPlugins: TComboBox + AnchorSideLeft.Control = tabCredentials AnchorSideTop.Control = editRepeatPassword AnchorSideTop.Side = asrBottom AnchorSideRight.Control = tabCredentials AnchorSideRight.Side = asrBottom Left = 150 Height = 23 - Top = 117 + Top = 137 Width = 222 Anchors = [akTop, akLeft, akRight] + BorderSpacing.Left = 145 BorderSpacing.Around = 5 ItemHeight = 15 Style = csDropDownList TabOrder = 5 - OnChange = Modification + OnChange = comboPluginsChange + end + object lblPasswordHint: TLabel + AnchorSideLeft.Control = tabCredentials + AnchorSideTop.Control = editPassword + AnchorSideTop.Side = asrBottom + Left = 150 + Height = 15 + Hint = 'You changed the authentication plugin. This will reset the password, so please specify a new one.' + Top = 89 + Width = 196 + BorderSpacing.Left = 145 + BorderSpacing.Around = 5 + Caption = 'Empty password after plugin change!' + ParentShowHint = False + ShowHint = True + Visible = False end end object tabLimitations: TTabSheet AutoSize = True Caption = 'Limitations' - ClientHeight = 173 + ClientHeight = 193 ClientWidth = 377 ImageIndex = 1 object lblMaxQueries: TLabel @@ -481,7 +509,7 @@ object UserManagerForm: TUserManagerForm object tabSSL: TTabSheet AutoSize = True Caption = 'SSL options' - ClientHeight = 173 + ClientHeight = 193 ClientWidth = 377 ImageIndex = 2 object lblCipher: TLabel @@ -593,8 +621,8 @@ object UserManagerForm: TUserManagerForm end object PageControlAccess: TPageControl Left = 0 - Height = 122 - Top = 201 + Height = 102 + Top = 221 Width = 385 ActivePage = tabPrivileges Align = alClient @@ -602,11 +630,11 @@ object UserManagerForm: TUserManagerForm TabOrder = 1 object tabPrivileges: TTabSheet Caption = 'Privileges' - ClientHeight = 94 + ClientHeight = 74 ClientWidth = 377 object treePrivs: TLazVirtualStringTree Left = 0 - Height = 66 + Height = 46 Top = 28 Width = 377 Align = alClient @@ -657,11 +685,11 @@ object UserManagerForm: TUserManagerForm end object tabRoles: TTabSheet Caption = 'Roles' - ClientHeight = 94 + ClientHeight = 74 ClientWidth = 377 object ValueListEditorRoles: TValueListEditor Left = 0 - Height = 94 + Height = 74 Top = 0 Width = 377 Align = alClient diff --git a/source/usermanager.pas b/source/usermanager.pas index d2d231c80..f8ea75b0c 100644 --- a/source/usermanager.pas +++ b/source/usermanager.pas @@ -64,6 +64,7 @@ TUserManagerForm = class(TExtForm) btnCancel: TSpeedButton; btnSave: TSpeedButton; comboPlugins: TComboBox; + lblPasswordHint: TLabel; lblPlugin: TLabel; pnlBottom: TPanel; pnlLeft: TPanel; @@ -136,6 +137,7 @@ TUserManagerForm = class(TExtForm) lblDefaultRole: TLabel; comboDefaultRole: TComboBox; procedure btnCancelClick(Sender: TObject); + procedure comboPluginsChange(Sender: TObject); procedure editFromHostButtonClick(Sender: TObject); procedure editPasswordButtonClick(Sender: TObject); procedure FormCreate(Sender: TObject); @@ -197,7 +199,7 @@ TUserManagerForm = class(TExtForm) private { Private declarations } FUsers: TUserList; - FModified, FAdded: Boolean; + FModified, FAdded, FPluginModified: Boolean; FHasIsRole, FHasDefaultRole: Boolean; FHasPlugin: Boolean; FPlugins: TStringList; @@ -208,7 +210,9 @@ TUserManagerForm = class(TExtForm) FColorReadPriv, FColorWritePriv, FColorAdminPriv: TColor; FSQLPluginPrefix, FSQLPluginPassPrefix: String; procedure SetModified(Value: Boolean); + procedure SetPluginModified(Value: Boolean); property Modified: Boolean read FModified write SetModified; + property PluginModified: Boolean read FPluginModified write SetPluginModified; function GetPrivByNode(Node: PVirtualNode): TPrivObj; function SelectUserNode(User: TUser): Boolean; public @@ -285,6 +289,12 @@ procedure TUserManagerForm.btnCancelClick(Sender: TObject); ModalResult := mrCancel; end; +procedure TUserManagerForm.comboPluginsChange(Sender: TObject); +begin + PluginModified := True; + Modification(Sender); +end; + procedure TUserManagerForm.editFromHostButtonClick(Sender: TObject); begin ShowPopup(editFromHost.Button, menuHost); @@ -477,6 +487,7 @@ procedure TUserManagerForm.FormShow(Sender: TObject); InvalidateVT(listUsers, VTREE_NOTLOADED, False); FPrivObjects := TPrivObjList.Create(TPrivComparer.Create, True); Modified := False; + PluginModified := False; FAdded := False; tabRoles.TabVisible := FHasIsRole; listUsers.OnFocusChanged(listUsers, listUsers.FocusedNode, listUsers.FocusedColumn); @@ -518,6 +529,23 @@ procedure TUserManagerForm.SetModified(Value: Boolean); listUsers.Invalidate; end; +procedure TUserManagerForm.SetPluginModified(Value: Boolean); +var + User: PUser; +begin + if not Assigned(listUsers.FocusedNode) then + Exit; + FPluginModified := Value; + if not FAdded then begin + lblPasswordHint.Visible := FPluginModified; + if Value then begin + User := listUsers.GetNodeData(listUsers.FocusedNode); + User.Password := ''; + editPassword.TextHint := ''; + end; + end; +end; + procedure TUserManagerForm.Modification(Sender: TObject); var @@ -614,6 +642,7 @@ procedure TUserManagerForm.listUsersFocusChanging(Sender: TBaseVirtualTree; OldN mrNo: begin Allowed := True; Modified := False; + PluginModified := False; if FAdded then btnDeleteUser.Click; end; @@ -721,6 +750,7 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: end; MessageDialog(Msg, mtError, [mbOK]); FModified := False; + FPluginModified := False; SelectNode(listUsers, nil); Exit; end; @@ -916,6 +946,7 @@ procedure TUserManagerForm.listUsersFocusChanged(Sender: TBaseVirtualTree; Node: // Populate privilege tree Modified := False; + PluginModified := False; treePrivs.FocusedNode := nil; treePrivs.Clear; treePrivs.RootNodeCount := FPrivObjects.Count; @@ -1509,7 +1540,7 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); // General user options if (P.DBObj.NodeType = lntNone) and FocusedUser.IsUser then begin // Plugin - if comboPlugins.ItemIndex > 0 then begin + if FPluginModified and (comboPlugins.ItemIndex > 0) then begin FConnection.Query('ALTER USER ' + UserHost + ' IDENTIFIED ' + FSQLPluginPrefix + ' ' + comboPlugins.Text); FConnection.ShowWarnings; end; @@ -1606,6 +1637,7 @@ procedure TUserManagerForm.btnSaveClick(Sender: TObject); FConnection.Query(qReloadPrivileges); Modified := False; + PluginModified := False; FAdded := False; FocusedUser.Username := editUsername.Text; FocusedUser.Host := editFromHost.Text; @@ -1696,6 +1728,7 @@ procedure TUserManagerForm.btnDiscardClick(Sender: TObject); begin // Reset modifications Modified := False; + PluginModified := False; listUsers.OnFocusChanged(listUsers, listUsers.FocusedNode, listUsers.FocusedColumn); end;