From 48e3dc0b8145fee6229e764bec499f8d644c80c2 Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Wed, 16 Apr 2025 15:37:14 +0200 Subject: [PATCH] Issue #2137: enable automatic tab restore --- source/apphelpers.pas | 28 +++++++--------- source/extra_controls.pas | 36 +++++++++++---------- source/main.lfm | 22 +++++++++++-- source/main.pas | 67 +++++++++++++++++++++++---------------- 4 files changed, 91 insertions(+), 62 deletions(-) diff --git a/source/apphelpers.pas b/source/apphelpers.pas index 0298d12a..e20994d7 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -15,7 +15,6 @@ type // Sync with main branch and Delphi structures TSynMemo = TSynEdit; TVirtualStringTree = TLazVirtualStringTree; - TExtFileSaveDialog = class(TSaveDialog); TImageIndex = Integer; PInt = ^Integer; TProgressBarState = (pbsNormal, pbsError, pbsPaused); @@ -441,7 +440,7 @@ type //function GetCurrentPackageFullName(out Len: Cardinal; Name: PWideChar): Integer; stdcall; external kernel32 delayed; function GetThemeColor(Color: TColor): TColor; function ThemeIsDark(ThemeName: String=''): Boolean; - //function ProcessExists(pid: Cardinal; ExeNamePattern: String): Boolean; + function ProcessExists(pid: Cardinal; ExeNamePattern: String): Boolean; //procedure ToggleCheckBoxWithoutClick(chk: TCheckBox; State: Boolean); function SynCompletionProposalPrettyText(ImageIndex: Integer; LeftText, CenterText, RightText: String; LeftColor: TColor=-1; CenterColor: TColor=-1; RightColor: TColor=-1): String; function PopupComponent(Sender: TObject): TComponent; @@ -663,15 +662,9 @@ end; function DeleteFileWithUndo(sFileName: string): Boolean; -//var -// fos: TSHFileOpStruct; begin - {FillChar(fos, SizeOf(fos), 0); - fos.wFunc := FO_DELETE; - fos.pFrom := PChar(sFileName + #0); - fos.fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_SILENT; - Result := (0 = ShFileOperation(fos));} - Beep; + // Todo: move to trash, cross-platform + Result := DeleteFile(sFileName); end; @@ -2767,6 +2760,7 @@ var hFile: DWORD; begin // Check if file is writable + // replaced through LazFileUtils.FileIsWritable if not FileExists(FilePath) then begin // Return true if file does not exist Result := True; @@ -2792,14 +2786,16 @@ begin end; -{function ProcessExists(pid: Cardinal; ExeNamePattern: String): Boolean; -var +function ProcessExists(pid: Cardinal; ExeNamePattern: String): Boolean; +{var Proc: TProcessEntry32; SnapShot: THandle; - ContinueLoop: Boolean; + ContinueLoop: Boolean;} begin // Check if a given process id exists - SnapShot := CreateToolhelp32Snapshot(TH32CS_SnapProcess, 0); + // Todo: convert code for cross-platforming + Result := False; + {SnapShot := CreateToolhelp32Snapshot(TH32CS_SnapProcess, 0); Proc.dwSize := Sizeof(Proc); Result := False; ContinueLoop := Process32First(SnapShot, Proc); @@ -2809,8 +2805,8 @@ begin Break; ContinueLoop := Process32Next(SnapShot, Proc); end; - CloseHandle(Snapshot); -end;} + CloseHandle(Snapshot);} +end; {procedure ToggleCheckBoxWithoutClick(chk: TCheckBox; State: Boolean); diff --git a/source/extra_controls.pas b/source/extra_controls.pas index c42019fa..360975a0 100644 --- a/source/extra_controls.pas +++ b/source/extra_controls.pas @@ -59,22 +59,23 @@ type property EncodingIndex: Cardinal read FEncodingIndex write FEncodingIndex; end; - {TExtFileSaveDialog = class(TFileSaveDialog) + TExtFileSaveDialog = class(TSaveDialog) private + FFilters: TStringList; FLineBreaks: TStringList; FLineBreakIndex: TLineBreaks; const idLineBreakCombo = 1; procedure FileOkClickNoOp(Sender: TObject; var CanClose: Boolean); protected - procedure DoOnExecute; override; - function DoOnFileOkClick: Boolean; override; + //procedure DoOnExecute; override; + //function DoOnFileOkClick: Boolean; override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure AddFileType(FileMask, DisplayName: String); property LineBreaks: TStringList read FLineBreaks; property LineBreakIndex: TLineBreaks read FLineBreakIndex write FLineBreakIndex; - end;} + end; {TExtSynHotKey = class(TSynHotKey) private @@ -502,14 +503,10 @@ end; procedure TExtFileOpenDialog.AddFileType(FileMask, DisplayName: String); var - //FileType: TFileTypeItem; i: Integer; NewFilter: String; begin // Shorthand for callers - {FileType := FileTypes.Add; - FileType.DisplayName := DisplayName; - FileType.FileMask := FileMask;} FFilters.Values[DisplayName] := FileMask; NewFilter := ''; for i:=FFilters.Count-1 downto 0 do begin @@ -567,7 +564,7 @@ end;} { TExtFileSaveDialog } -{constructor TExtFileSaveDialog.Create(AOwner: TComponent); +constructor TExtFileSaveDialog.Create(AOwner: TComponent); begin inherited Create(AOwner); FLineBreaks := TStringList.Create; @@ -575,6 +572,7 @@ begin FLineBreaks.Add(_('UNIX linebreaks')); FLineBreaks.Add(_('Mac OS linebreaks')); FLineBreakIndex := lbsWindows; + FFilters := TStringList.Create; end; @@ -587,16 +585,22 @@ end; procedure TExtFileSaveDialog.AddFileType(FileMask, DisplayName: String); var - FileType: TFileTypeItem; + i: Integer; + NewFilter: String; begin // Shorthand for callers - FileType := FileTypes.Add; - FileType.DisplayName := DisplayName; - FileType.FileMask := FileMask; + FFilters.Values[DisplayName] := FileMask; + NewFilter := ''; + for i:=FFilters.Count-1 downto 0 do begin + NewFilter := NewFilter + Format('%s (%s)|%s', [FFilters.Names[i], FFilters.ValueFromIndex[i], FFilters.ValueFromIndex[i]]); + if i > 0 then + NewFilter := NewFilter + '|'; + end; + Filter := NewFilter; end; -procedure TExtFileSaveDialog.DoOnExecute; +{procedure TExtFileSaveDialog.DoOnExecute; var iCustomize: IFileDialogCustomize; i, ComboIndex: Integer; @@ -622,7 +626,7 @@ begin iCustomize.EndVisualGroup; end; end; -end; +end;} procedure TExtFileSaveDialog.FileOkClickNoOp(Sender: TObject; var CanClose: Boolean); @@ -631,7 +635,7 @@ begin end; -function TExtFileSaveDialog.DoOnFileOkClick: Boolean; +{function TExtFileSaveDialog.DoOnFileOkClick: Boolean; var iCustomize: IFileDialogCustomize; ComboIndex: Cardinal; diff --git a/source/main.lfm b/source/main.lfm index 2e126e25..916ae10b 100644 --- a/source/main.lfm +++ b/source/main.lfm @@ -1089,12 +1089,12 @@ object MainForm: TMainForm Height = 315 Top = 0 Width = 780 - ActivePage = tabData + ActivePage = tabQuery Align = alClient HotTrack = True Images = ImageListIcons8 PopupMenu = popupMainTabs - TabIndex = 3 + TabIndex = 4 TabOrder = 1 OnChange = PageControlMainChange OnChanging = PageControlMainChanging @@ -3019,7 +3019,7 @@ object MainForm: TMainForm OnDragOver = SynMemoQueryDragOver OnKeyPress = SynMemoQueryKeyPress OnMouseWheel = AnySynMemoMouseWheel - Gutter.Width = 30 + Gutter.Width = 68 Gutter.MouseActions = <> RightGutter.Width = 0 RightGutter.MouseActions = <> @@ -3473,6 +3473,10 @@ object MainForm: TMainForm OnSpecialLineColors = SynMemoQuerySpecialLineColors OnStatusChange = SynMemoQueryStatusChange inline SynLeftGutterPartList1: TSynGutterPartList + object SynGutterMarks1: TSynGutterMarks + Width = 30 + MouseActions = <> + end object SynGutterLineNumber1: TSynGutterLineNumber Width = 17 MouseActions = <> @@ -3483,6 +3487,18 @@ object MainForm: TMainForm ZeroStart = False LeadingZeros = False end + object SynGutterChanges1: TSynGutterChanges + Width = 5 + MouseActions = <> + ModifiedColor = 59900 + SavedColor = clGreen + end + object SynGutterSeparator1: TSynGutterSeparator + Width = 3 + MouseActions = <> + MarkupInfo.Background = clWhite + MarkupInfo.Foreground = clGray + end object SynGutterCodeFolding1: TSynGutterCodeFolding Width = 13 MouseActions = <> diff --git a/source/main.pas b/source/main.pas index 5da21f2f..0ed7b1d0 100644 --- a/source/main.pas +++ b/source/main.pas @@ -14,7 +14,7 @@ uses Generics.Defaults, opensslsockets, StdActns, Clipbrd, Types, LCLType, EditBtn, FileUtil, LMessages, jsonconf, dbconnection, dbstructures, dbstructures.mysql, generic_types, apphelpers, extra_controls, createdatabase, - SynEditMarkupSpecialLine, searchreplace, ImgList; + SynEditMarkupSpecialLine, searchreplace, ImgList, IniFiles, LazFileUtils; type @@ -1298,9 +1298,9 @@ type //procedure SetLogToFile(Value: Boolean); procedure StoreLastSessions; function HandleUnixTimestampColumn(Sender: TBaseVirtualTree; Column: TColumnIndex): Boolean; - //function InitTabsIniFile: TIniFile; - //procedure StoreTabs; - //function RestoreTabs: Boolean; + function InitTabsIniFile: TIniFile; + procedure StoreTabs; + function RestoreTabs: Boolean; procedure SetHintFontByControl(Control: TWinControl=nil); public QueryTabs: TQueryTabList; @@ -2331,10 +2331,10 @@ begin end; end; - {// Restore backup'ed query tabs + // Restore backup'ed query tabs if AppSettings.RestoreTabsInitValue then begin TimerStoreTabs.Enabled := RestoreTabs; - end;} + end; // Load SQL file(s) by command line if not RunQueryFiles(FileNames, nil, false) then begin @@ -2353,10 +2353,10 @@ begin end; -{function TMainForm.InitTabsIniFile: TIniFile; +function TMainForm.InitTabsIniFile: TIniFile; var - WaitingSince: UInt64; - Attempts: Integer; + //WaitingSince: UInt64; + //Attempts: Integer; TabsIniFilename: String; begin // Try to open tabs.ini for writing or reading @@ -2365,7 +2365,7 @@ begin TabsIniFilename := ExtractFilePath(Application.ExeName) + 'tabs.ini' else TabsIniFilename := AppSettings.DirnameUserAppData + 'tabs.ini'; - WaitingSince := GetTickCount64; + {WaitingSince := GetTickCount64; Attempts := 0; while not FileIsWritable(TabsIniFilename) do begin if GetTickCount64 - WaitingSince > 3000 then @@ -2375,16 +2375,16 @@ begin end; if Attempts > 0 then begin LogSQL(Format('Had to wait %d ms before opening %s', [GetTickCount64 - WaitingSince, TabsIniFilename]), lcDebug); - end; + end;} // Catch errors when file cannot be created if not FileExists(TabsIniFilename) then begin SaveUnicodeFile(TabsIniFilename, '', UTF8NoBOMEncoding); end; Result := TIniFile.Create(TabsIniFilename); -end;} +end; -{procedure TMainForm.StoreTabs; +procedure TMainForm.StoreTabs; var Tab: TQueryTab; Section, TabCaption: String; @@ -2413,8 +2413,8 @@ begin TabsIni.WriteString(Section, TQueryTab.IdentFilename, Tab.MemoFilename); if TabsIni.ReadString(Section, TQueryTab.IdentCaption, '') <> TabCaption then TabsIni.WriteString(Section, TQueryTab.IdentCaption, TabCaption); - if TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0) <> Integer(GetCurrentProcessId) then - TabsIni.WriteInteger(Section, TQueryTab.IdentPid, Integer(GetCurrentProcessId)); + if TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0) <> Integer(GetProcessId) then + TabsIni.WriteInteger(Section, TQueryTab.IdentPid, Integer(GetProcessId)); if TabsIni.ReadInteger(Section, TQueryTab.IdentEditorHeight, 0) <> Tab.pnlMemo.Height then TabsIni.WriteInteger(Section, TQueryTab.IdentEditorHeight, Tab.pnlMemo.Height); if TabsIni.ReadInteger(Section, TQueryTab.IdentHelpersWidth, 0) <> Tab.pnlHelpers.Width then @@ -2444,7 +2444,7 @@ begin end; // Delete tab section if tab was closed and section belongs to this app instance pid := Cardinal(TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0)); - if (not SectionTabExists) and (pid = GetCurrentProcessId) then begin + if (not SectionTabExists) and (pid = GetProcessId) then begin TabsIni.EraseSection(Section); end; end; @@ -2457,14 +2457,14 @@ begin ErrorDialog(_('Storing tab setup failed'), 'Tabs won''t be stored in this session.' + CRLF + CRLF + E.Message + CRLF + CRLF + - SysErrorMessage(GetLastError) + SysErrorMessage(GetLastOSError) ); end; end; -end;} +end; -{function TMainForm.RestoreTabs: Boolean; +function TMainForm.RestoreTabs: Boolean; var Tab: TQueryTab; Sections, SlowTabs: TStringList; @@ -2513,7 +2513,7 @@ begin Encoding := GetEncodingByName(TabsIni.ReadString(Section, TQueryTab.IdentFileEncoding, 'UTF-8')); // Don't restore this tab if it belongs to a different running Heidi process - if (pid > 0) and (pid <> GetCurrentProcessId) and ProcessExists(pid, APPNAME) then begin + if (pid > 0) and (pid <> GetProcessId) and ProcessExists(pid, APPNAME) then begin LogSQL(IfThen(BackupFilename.IsEmpty, Filename, BackupFilename)+' loaded in process #'+pid.ToString); Continue; end; @@ -2590,11 +2590,11 @@ begin ErrorDialog(_('Restoring tab setup failed'), 'Tabs won''t be stored in this session.' + CRLF + CRLF + E.Message + CRLF + CRLF + - SysErrorMessage(GetLastError) + SysErrorMessage(GetLastOSError) ); end; end; -end;} +end; procedure TMainForm.SetHintFontByControl(Control: TWinControl=nil); @@ -2614,7 +2614,7 @@ end; procedure TMainForm.TimerStoreTabsTimer(Sender: TObject); begin // Backup unsaved content every 10 seconds - //StoreTabs; + StoreTabs; end; @@ -2627,6 +2627,7 @@ begin Dialog.Free; end; + procedure TMainForm.actDisconnectExecute(Sender: TObject); var Connection: TDBConnection; @@ -12328,7 +12329,19 @@ begin QueryTab.Memo.RightEdge := SynMemoQuery.RightEdge; QueryTab.Memo.WantTabs := SynMemoQuery.WantTabs; QueryTab.Memo.Highlighter := SynMemoQuery.Highlighter; - QueryTab.Memo.Gutter.Assign(SynMemoQuery.Gutter); + QueryTab.Memo.Gutter.Width := SynMemoQuery.Gutter.Width; + // Todo: adding and gutter part results in EAccessviolation's when closing a query tab. + // Fix that and use the same gutter as in mother query tab + // In the meantime we let SynEdit use TSynGutter.CreateDefaultGutterParts + //for GutterPartIndex:=0 to SynMemoQuery.Gutter.Parts.Count-1 do begin + // QueryTab.Memo.Gutter.Parts.Add(SynMemoQuery.Gutter.Parts[GutterPartIndex].ClassType.Create(QueryTab.Memo.Gutter.Parts)); + // QueryTab.Memo.Gutter.Parts[GutterPartIndex].Width := SynMemoQuery.Gutter.Parts[GutterPartIndex].Width; + //end; + //QueryTab.Memo.Gutter.Parts.Clear; + //QueryTab.Memo.Gutter.Parts.Add(TSynGutterLineNumber.Create(QueryTab.Memo.Gutter.Parts)); + //QueryTab.Memo.Gutter.Parts[0].Width := SynMemoQuery.Gutter.Parts[0].Width; + //QueryTab.Memo.Gutter.Parts.Add(TSynGutterCodeFolding.Create(QueryTab.Memo.Gutter.Parts)); + //QueryTab.Memo.Gutter.Parts[1].Width := SynMemoQuery.Gutter.Parts[1].Width; QueryTab.Memo.Font.Assign(SynMemoQuery.Font); //QueryTab.Memo.ActiveLineColor := SynMemoQuery.ActiveLineColor; QueryTab.Memo.OnStatusChange := SynMemoQuery.OnStatusChange; @@ -13123,7 +13136,7 @@ begin end; end; // Leave space for close button on closable query tabs - Text := Text + ' '; + //Text := Text + ' '; end; PageControlMain.Pages[PageIndex].Caption := Text; FixQueryTabCloseButtons; @@ -13186,8 +13199,8 @@ begin else begin Dialog := TExtFileSaveDialog.Create(Self); Dialog.Options := Dialog.Options + [ofOverwritePrompt]; - //Dialog.AddFileType('*.sql', _('SQL files')); - //Dialog.AddFileType('*.*', _('All files')); + Dialog.AddFileType('*.sql', _('SQL files')); + Dialog.AddFileType('*.*', _('All files')); Dialog.DefaultExt := 'sql'; //Dialog.LineBreakIndex := Tab.MemoLineBreaks; if Dialog.Execute then begin