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
OnExecute = actNewQueryTabExecute
end
object actNewQueryTabNofocus: TAction
Category = 'File'
Caption = 'New query tab in background'
OnExecute = actNewQueryTabExecute
end
object actCloseQueryTab: TAction
Category = 'File'
Caption = 'Close query tab'

View File

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