Enhance tab restore feature:

* restore scroll position (EditorTopLine)
* restore active query tab (TabFocused)
* code cleanup: move identifier literals for tabs.ini to class constants
This commit is contained in:
Ansgar Becker
2020-12-08 20:36:57 +01:00
parent ad91f12e62
commit 172ed7b725
2 changed files with 91 additions and 55 deletions

View File

@ -2982,6 +2982,11 @@ object MainForm: TMainForm
ShortCut = 16468 ShortCut = 16468
OnExecute = actNewQueryTabExecute OnExecute = actNewQueryTabExecute
end end
object actNewQueryTabNofocus: TAction
Category = 'File'
Caption = 'New query tab in background'
OnExecute = actNewQueryTabExecute
end
object actCloseQueryTab: TAction object actCloseQueryTab: TAction
Category = 'File' Category = 'File'
Caption = 'Close query tab' Caption = 'Close query tab'

View File

@ -57,6 +57,16 @@ type
end; end;
TResultTabs = TObjectList<TResultTab>; TResultTabs = TObjectList<TResultTab>;
TQueryTab = class(TComponent) TQueryTab = class(TComponent)
const
IdentBackupFilename = 'BackupFilename';
IdentFilename = 'Filename';
IdentCaption = 'Caption';
IdentPid = 'pid';
IdentEditorHeight = 'EditorHeight';
IdentHelpersWidth = 'HelpersWidth';
IdentBindParams = 'BindParams';
IdentEditorTopLine = 'EditorTopLine';
IdentTabFocused = 'TabFocused';
private private
FMemo: TSynMemo; FMemo: TSynMemo;
FMemoFilename: String; FMemoFilename: String;
@ -729,6 +739,7 @@ type
actRenameQueryTab: TAction; actRenameQueryTab: TAction;
menuRenameQueryTab: TMenuItem; menuRenameQueryTab: TMenuItem;
Renametab1: TMenuItem; Renametab1: TMenuItem;
actNewQueryTabNofocus: TAction;
procedure actCreateDBObjectExecute(Sender: TObject); procedure actCreateDBObjectExecute(Sender: TObject);
procedure menuConnectionsPopup(Sender: TObject); procedure menuConnectionsPopup(Sender: TObject);
procedure actExitApplicationExecute(Sender: TObject); procedure actExitApplicationExecute(Sender: TObject);
@ -930,7 +941,7 @@ type
function GetMainTabAt(X, Y: Integer): Integer; function GetMainTabAt(X, Y: Integer): Integer;
procedure FixQueryTabCloseButtons; procedure FixQueryTabCloseButtons;
function ActiveQueryTab: TQueryTab; function ActiveQueryTab: TQueryTab;
function GetOrCreateEmptyQueryTab: TQueryTab; function GetOrCreateEmptyQueryTab(DoFocus: Boolean): TQueryTab;
function GetQueryTabByNumber(Number: Integer): TQueryTab; function GetQueryTabByNumber(Number: Integer): TQueryTab;
function GetQueryTabByHelpers(FindTree: TBaseVirtualTree): TQueryTab; function GetQueryTabByHelpers(FindTree: TBaseVirtualTree): TQueryTab;
function ActiveQueryMemo: TSynMemo; function ActiveQueryMemo: TSynMemo;
@ -2270,7 +2281,7 @@ begin
// Load SQL file(s) by command line // Load SQL file(s) by command line
if not RunQueryFiles(FileNames, nil, false) then begin if not RunQueryFiles(FileNames, nil, false) then begin
for i:=0 to FileNames.Count-1 do begin for i:=0 to FileNames.Count-1 do begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(False);
Tab.LoadContents(FileNames[i], True, nil); Tab.LoadContents(FileNames[i], True, nil);
if i = FileNames.Count-1 then if i = FileNames.Count-1 then
SetMainTab(Tab.TabSheet); SetMainTab(Tab.TabSheet);
@ -2328,27 +2339,29 @@ begin
Tab.BackupUnsavedContent; Tab.BackupUnsavedContent;
Section := Tab.Uid; Section := Tab.Uid;
if Tab.Memo.GetTextLen > 0 then begin // Avoid writing the tabs.ini file if nothing was effectively changed
// Avoid writing the tabs.ini file if nothing was effectively changed TabCaption := Tab.TabSheet.Caption;
TabCaption := Tab.TabSheet.Caption; TabCaption := TabCaption.Trim([' ','*']);
TabCaption := TabCaption.Trim([' ','*']); if ExecRegExpr('^'+QuoteRegExprMetaChars(_('Query')+' #')+'\d+$', TabCaption) then
if ExecRegExpr('^'+QuoteRegExprMetaChars(_('Query')+' #')+'\d+$', TabCaption) then TabCaption := '';
TabCaption := ''; if TabsIni.ReadString(Section, TQueryTab.IdentBackupFilename, '') <> Tab.MemoBackupFilename then
if TabsIni.ReadString(Section, 'BackupFilename', '') <> Tab.MemoBackupFilename then TabsIni.WriteString(Section, TQueryTab.IdentBackupFilename, Tab.MemoBackupFilename);
TabsIni.WriteString(Section, 'BackupFilename', Tab.MemoBackupFilename); if TabsIni.ReadString(Section, TQueryTab.IdentFilename, '') <> Tab.MemoFilename then
if TabsIni.ReadString(Section, 'Filename', '') <> Tab.MemoFilename then TabsIni.WriteString(Section, TQueryTab.IdentFilename, Tab.MemoFilename);
TabsIni.WriteString(Section, 'Filename', Tab.MemoFilename); if TabsIni.ReadString(Section, TQueryTab.IdentCaption, '') <> TabCaption then
if TabsIni.ReadString(Section, 'Caption', '') <> TabCaption then TabsIni.WriteString(Section, TQueryTab.IdentCaption, TabCaption);
TabsIni.WriteString(Section, 'Caption', TabCaption); if TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0) <> Integer(GetCurrentProcessId) then
if TabsIni.ReadInteger(Section, 'pid', 0) <> Integer(GetCurrentProcessId) then TabsIni.WriteInteger(Section, TQueryTab.IdentPid, Integer(GetCurrentProcessId));
TabsIni.WriteInteger(Section, 'pid', Integer(GetCurrentProcessId)); if TabsIni.ReadInteger(Section, TQueryTab.IdentEditorHeight, 0) <> Tab.pnlMemo.Height then
if TabsIni.ReadInteger(Section, 'EditorHeight', 0) <> Tab.pnlMemo.Height then TabsIni.WriteInteger(Section, TQueryTab.IdentEditorHeight, Tab.pnlMemo.Height);
TabsIni.WriteInteger(Section, 'EditorHeight', Tab.pnlMemo.Height); if TabsIni.ReadInteger(Section, TQueryTab.IdentHelpersWidth, 0) <> Tab.pnlHelpers.Width then
if TabsIni.ReadInteger(Section, 'HelpersWidth', 0) <> Tab.pnlHelpers.Width then TabsIni.WriteInteger(Section, TQueryTab.IdentHelpersWidth, Tab.pnlHelpers.Width);
TabsIni.WriteInteger(Section, 'HelpersWidth', Tab.pnlHelpers.Width); if TabsIni.ReadString(Section, TQueryTab.IdentBindParams, '') <> Tab.ListBindParams.AsText then
if TabsIni.ReadString(Section, 'BindParams', '') <> Tab.ListBindParams.AsText then TabsIni.WriteString(Section, TQueryTab.IdentBindParams, Tab.ListBindParams.AsText);
TabsIni.WriteString(Section, 'BindParams', Tab.ListBindParams.AsText); if TabsIni.ReadInteger(Section, TQueryTab.IdentEditorTopLine, 1) <> Tab.Memo.TopLine then
end; TabsIni.WriteInteger(Section, TQueryTab.IdentEditorTopLine, Tab.Memo.TopLine);
if TabsIni.ReadBool(Section, TQueryTab.IdentTabFocused, False) <> (Tab.TabSheet = Tab.TabSheet.PageControl.ActivePage) then
TabsIni.WriteBool(Section, TQueryTab.IdentTabFocused, (Tab.TabSheet = Tab.TabSheet.PageControl.ActivePage));
end; end;
// Tabs with deleted backup files don't get restored anyway. But a section from a closed user loaded tab // Tabs with deleted backup files don't get restored anyway. But a section from a closed user loaded tab
@ -2365,7 +2378,7 @@ begin
end; end;
end; end;
// Delete tab section if tab was closed and section belongs to this app instance // Delete tab section if tab was closed and section belongs to this app instance
pid := Cardinal(TabsIni.ReadInteger(Section, 'pid', 0)); pid := Cardinal(TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0));
if (not SectionTabExists) and (pid = GetCurrentProcessId) then begin if (not SectionTabExists) and (pid = GetCurrentProcessId) then begin
TabsIni.EraseSection(Section); TabsIni.EraseSection(Section);
end; end;
@ -2393,8 +2406,9 @@ var
Section, Filename, BackupFilename, TabCaption: String; Section, Filename, BackupFilename, TabCaption: String;
TabsIni: TIniFile; TabsIni: TIniFile;
pid: Cardinal; pid: Cardinal;
EditorHeight, HelpersWidth: Integer; EditorHeight, HelpersWidth, EditorTopLine: Integer;
BindParams: String; BindParams: String;
TabFocused: Boolean;
begin begin
// Restore query tab setup from tabs.ini // Restore query tab setup from tabs.ini
Result := True; Result := True;
@ -2408,13 +2422,15 @@ begin
for Section in Sections do begin for Section in Sections do begin
Filename := TabsIni.ReadString(Section, 'Filename', ''); Filename := TabsIni.ReadString(Section, TQueryTab.IdentFilename, '');
BackupFilename := TabsIni.ReadString(Section, 'BackupFilename', ''); BackupFilename := TabsIni.ReadString(Section, TQueryTab.IdentBackupFilename, '');
TabCaption := TabsIni.ReadString(Section, 'Caption', ''); TabCaption := TabsIni.ReadString(Section, TQueryTab.IdentCaption, '');
pid := Cardinal(TabsIni.ReadInteger(Section, 'pid', 0)); pid := Cardinal(TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0));
EditorHeight := TabsIni.ReadInteger(Section, 'EditorHeight', 0); EditorHeight := TabsIni.ReadInteger(Section, TQueryTab.IdentEditorHeight, 0);
HelpersWidth := TabsIni.ReadInteger(Section, 'HelpersWidth', 0); HelpersWidth := TabsIni.ReadInteger(Section, TQueryTab.IdentHelpersWidth, 0);
BindParams := TabsIni.ReadString(Section, 'BindParams', ''); BindParams := TabsIni.ReadString(Section, TQueryTab.IdentBindParams, '');
EditorTopLine := TabsIni.ReadInteger(Section, TQueryTab.IdentEditorTopLine, 1);
TabFocused := TabsIni.ReadBool(Section, TQueryTab.IdentTabFocused, False);
// Don't restore this tab if it belongs to a different running Heidi process // 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 <> GetCurrentProcessId) and ProcessExists(pid, APPNAME) then begin
@ -2426,7 +2442,7 @@ begin
// Both of them may not exist. // Both of them may not exist.
if not BackupFilename.IsEmpty then begin if not BackupFilename.IsEmpty then begin
if FileExists(BackupFilename) then begin if FileExists(BackupFilename) then begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(False);
Tab.Uid := Section; Tab.Uid := Section;
Tab.LoadContents(BackupFilename, True, UTF8NoBOMEncoding); Tab.LoadContents(BackupFilename, True, UTF8NoBOMEncoding);
Tab.MemoFilename := Filename; Tab.MemoFilename := Filename;
@ -2439,13 +2455,16 @@ begin
Tab.pnlHelpers.Width := HelpersWidth; Tab.pnlHelpers.Width := HelpersWidth;
Tab.ListBindParams.AsText := BindParams; Tab.ListBindParams.AsText := BindParams;
Tab.BindParamsActivated := Tab.ListBindParams.Count > 0; Tab.BindParamsActivated := Tab.ListBindParams.Count > 0;
Tab.Memo.TopLine := EditorTopLine;
if TabFocused then
SetMainTab(Tab.TabSheet);
end else begin end else begin
// Remove tab section if backup file is gone or inaccessible for some reason // Remove tab section if backup file is gone or inaccessible for some reason
TabsIni.EraseSection(Section); TabsIni.EraseSection(Section);
end; end;
end else if not Filename.IsEmpty then begin end else if not Filename.IsEmpty then begin
if FileExists(Filename) then begin if FileExists(Filename) then begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(False);
Tab.Uid := Section; Tab.Uid := Section;
Tab.LoadContents(Filename, True, nil); Tab.LoadContents(Filename, True, nil);
Tab.MemoFilename := Filename; Tab.MemoFilename := Filename;
@ -2457,6 +2476,9 @@ begin
Tab.pnlHelpers.Width := HelpersWidth; Tab.pnlHelpers.Width := HelpersWidth;
Tab.ListBindParams.AsText := BindParams; Tab.ListBindParams.AsText := BindParams;
Tab.BindParamsActivated := Tab.ListBindParams.Count > 0; Tab.BindParamsActivated := Tab.ListBindParams.Count > 0;
Tab.Memo.TopLine := EditorTopLine;
if TabFocused then
SetMainTab(Tab.TabSheet);
end else begin end else begin
// Remove tab section if user stored file was deleted by user // Remove tab section if user stored file was deleted by user
TabsIni.EraseSection(Section); TabsIni.EraseSection(Section);
@ -3842,7 +3864,7 @@ begin
Encoding := GetEncodingByName(Dialog.Encodings[Dialog.EncodingIndex]); Encoding := GetEncodingByName(Dialog.Encodings[Dialog.EncodingIndex]);
if not RunQueryFiles(Dialog.Files, Encoding, Sender=actRunSQL) then begin if not RunQueryFiles(Dialog.Files, Encoding, Sender=actRunSQL) then begin
for i:=0 to Dialog.Files.Count-1 do begin for i:=0 to Dialog.Files.Count-1 do begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(False);
Tab.LoadContents(Dialog.Files[i], True, Encoding); Tab.LoadContents(Dialog.Files[i], True, Encoding);
if i = Dialog.Files.Count-1 then if i = Dialog.Files.Count-1 then
SetMainTab(Tab.TabSheet); SetMainTab(Tab.TabSheet);
@ -4975,9 +4997,8 @@ begin
FileList := TStringList.Create; FileList := TStringList.Create;
FileList.Add(Filename); FileList.Add(Filename);
if not RunQueryFiles(FileList, nil, false) then begin if not RunQueryFiles(FileList, nil, false) then begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(True);
Tab.LoadContents(Filename, True, nil); Tab.LoadContents(Filename, True, nil);
SetMainTab(Tab.TabSheet);
end; end;
FileList.Free; FileList.Free;
end; end;
@ -7026,7 +7047,7 @@ begin
// query-memo - load their contents into seperate tabs // query-memo - load their contents into seperate tabs
if not RunQueryFiles(AFiles, nil, False) then begin if not RunQueryFiles(AFiles, nil, False) then begin
for i:=0 to AFiles.Count-1 do begin for i:=0 to AFiles.Count-1 do begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(True);
Tab.LoadContents(AFiles[i], False, nil); Tab.LoadContents(AFiles[i], False, nil);
end; end;
end; end;
@ -11454,7 +11475,9 @@ begin
SetupSynEditors; SetupSynEditors;
// Show new tab // Show new tab
SetMainTab(QueryTab.TabSheet); if Sender <> actNewQueryTabNofocus then begin
SetMainTab(QueryTab.TabSheet);
end;
end; end;
@ -11856,7 +11879,7 @@ begin
end; end;
function TMainForm.GetOrCreateEmptyQueryTab: TQueryTab; function TMainForm.GetOrCreateEmptyQueryTab(DoFocus: Boolean): TQueryTab;
var var
i: Integer; i: Integer;
begin begin
@ -11865,19 +11888,22 @@ begin
// or c) create a new one // or c) create a new one
// Result should never be nil, unlike in ActiveQueryTab // Result should never be nil, unlike in ActiveQueryTab
Result := nil; Result := nil;
// Search empty tab
for i:=0 to QueryTabs.Count-1 do begin
if (QueryTabs[i].MemoFilename='') and (QueryTabs[i].Memo.GetTextLen=0) then begin
Result := QueryTabs[i];
if DoFocus then
SetMainTab(Result.TabSheet);
Break;
end;
end;
// Create new tab
if Result = nil then begin if Result = nil then begin
// Search empty tab if DoFocus then
for i:=0 to QueryTabs.Count-1 do begin actNewQueryTabExecute(actNewQueryTab)
if (QueryTabs[i].MemoFilename='') and (QueryTabs[i].Memo.GetTextLen=0) then begin else
Result := QueryTabs[i]; actNewQueryTabExecute(actNewQueryTabNofocus);
break; Result := QueryTabs[QueryTabs.Count-1];
end;
end;
// Create new tab
if Result = nil then begin
actNewQueryTabExecute(Self);
Result := QueryTabs[QueryTabs.Count-1];
end;
end; end;
end; end;
@ -12086,8 +12112,13 @@ var
MsgButtons: TMsgDlgButtons; MsgButtons: TMsgDlgButtons;
begin begin
Tab := QueryTabs[PageIndex-tabQuery.PageIndex]; Tab := QueryTabs[PageIndex-tabQuery.PageIndex];
// Unhide tabsheet so the user sees the memo content
Tab.TabSheet.PageControl.ActivePage := Tab.TabSheet; // Unhide tabsheet so the user sees the memo content.
// If the dialog is suppressed anyway, the user does not need to see the text, and we avoid
// storing this as the focused tab
if AppSettings.ReadBool(asPromptSaveFileOnTabClose) then begin
Tab.TabSheet.PageControl.ActivePage := Tab.TabSheet;
end;
// Prompt for saving unsaved contents // Prompt for saving unsaved contents
if Tab.MemoFilename <> '' then if Tab.MemoFilename <> '' then
@ -12565,7 +12596,7 @@ begin
ParseCommandLine(ParamBlobToStr(Msg.CopyDataStruct.lpData), ConnectionParams, FileNames); ParseCommandLine(ParamBlobToStr(Msg.CopyDataStruct.lpData), ConnectionParams, FileNames);
if not RunQueryFiles(FileNames, nil, False) then begin if not RunQueryFiles(FileNames, nil, False) then begin
for i:=0 to FileNames.Count-1 do begin for i:=0 to FileNames.Count-1 do begin
Tab := GetOrCreateEmptyQueryTab; Tab := GetOrCreateEmptyQueryTab(True);
Tab.LoadContents(FileNames[i], True, nil); Tab.LoadContents(FileNames[i], True, nil);
end; end;
end; end;