Refactor code in SQL help dialog, replace the TTreeView with a VirtualTree.

This commit is contained in:
Ansgar Becker
2010-07-31 17:16:08 +00:00
parent b13ce034be
commit 25fb0ed266
5 changed files with 300 additions and 256 deletions

View File

@ -5,7 +5,7 @@ interface
uses
Windows, SysUtils, Classes, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ExtCtrls,
Buttons, SynMemo, SynEditHighlighter, SynHighlighterURI,
SynURIOpener, SynEdit,
SynURIOpener, SynEdit, VirtualTrees, Graphics,
mysql_connection;
type
@ -17,7 +17,7 @@ type
ButtonClose: TButton;
pnlMain: TPanel;
pnlLeft: TPanel;
treeTopics: TTreeView;
treeTopics: TVirtualStringTree;
editFilter: TButtonedEdit;
pnlRight: TPanel;
Splitter2: TSplitter;
@ -27,23 +27,34 @@ type
memoDescription: TSynMemo;
lblExample: TLabel;
MemoExample: TSynMemo;
timerSearch: TTimer;
procedure FormCreate(Sender: TObject);
procedure treeTopicsExpanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
procedure treeTopicsChange(Sender: TObject; Node: TTreeNode);
procedure FormShow(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure memosKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure ButtonOnlinehelpClick(Sender: TObject);
procedure ButtonCloseClick(Sender: TObject);
function ShowHelpItem: Boolean;
procedure fillTreeLevel( ParentNode: TTreeNode );
procedure findKeywordInTree;
procedure DoSearch(Sender: TObject);
procedure treeTopicsGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer);
procedure treeTopicsInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode;
var InitialStates: TVirtualNodeInitStates);
procedure treeTopicsInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode;
var ChildCount: Cardinal);
procedure treeTopicsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
procedure treeTopicsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
procedure treeTopicsFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
procedure treeTopicsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
procedure editFilterChange(Sender: TObject);
procedure editFilterRightButtonClick(Sender: TObject);
private
{ Private declarations }
FKeyword: String;
FRootTopics: TMySQLQuery;
function GetHelpResult(Node: PVirtualNode): TMySQLQuery;
procedure SetKeyword(Value: String);
public
{ Public declarations }
@ -65,22 +76,11 @@ uses helpers, main;
{$R *.dfm}
{**
FormCreate
}
procedure TfrmSQLhelp.FormCreate(Sender: TObject);
begin
// Set window-layout
InheritFont(Font);
SetWindowSizeGrip(Handle, True);
end;
{***
Startup
}
procedure TfrmSQLhelp.FormShow(Sender: TObject);
begin
// Set window-layout
Top := GetRegValue( REGNAME_SQLHELPWINTOP, Top );
Left := GetRegValue( REGNAME_SQLHELPWINLEFT, Left );
Width := GetRegValue( REGNAME_SQLHELPWINWIDTH, Width );
@ -89,191 +89,154 @@ begin
memoDescription.Height := GetRegValue( REGNAME_SQLHELPPRHEIGHT, memoDescription.Height );
Caption := DEFAULT_WINDOW_CAPTION;
MainForm.SetupSynEditors;
FixVT(treeTopics);
// Gather help contents for treeview with SQL: HELP "CONTENTS"
fillTreeLevel( nil );
treeTopics.Clear;
FreeAndNil(FRootTopics);
FRootTopics := Mainform.Connection.GetResults('HELP '+esc('CONTENTS'));
treeTopics.RootNodeCount := FRootTopics.RecordCount;
end;
{***
Fills exactly one level of the folder-tree
Call with NIL to generate the root folders,
then call recursively to iterate through all folders and fill them
@param TTreeNode Parent node to fill (or NIL)
}
procedure TfrmSQLhelp.fillTreeLevel( ParentNode: TTreeNode );
procedure TfrmSQLhelp.treeTopicsFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex);
var
tnode: TTreeNode;
Results: TMySQLQuery;
topic: String;
VT: TVirtualStringTree;
begin
if ParentNode = nil then begin
treeTopics.Items.Clear;
topic := 'CONTENTS';
end else begin
ParentNode.DeleteChildren;
topic := ParentNode.Text;
end;
try
Screen.Cursor := crHourglass;
Results := Mainform.Connection.GetResults( 'HELP "'+topic+'"' );
while not Results.Eof do begin
tnode := treeTopics.Items.AddChild( ParentNode, Results.Col('name'));
if (Results.Col('is_it_category', True) = 'Y') and ((ParentNode = nil) or (ParentNode.Text <> tnode.Text)) then begin
tnode.ImageIndex := ICONINDEX_CATEGORY_CLOSED;
tnode.SelectedIndex := ICONINDEX_CATEGORY_OPENED;
// Add a dummy item to show the plus-button so the user sees that there this
// is a category. When the plus-button is clicked, fetch the content of the category
treeTopics.Items.AddChild( tnode, DUMMY_NODE_TEXT );
end else begin
tnode.ImageIndex := ICONINDEX_HELPITEM;
tnode.SelectedIndex := tnode.ImageIndex;
end;
Results.Next;
end;
finally
FreeAndNil(Results);
Screen.Cursor := crDefault;
end;
end;
{***
Show selected keyword in Tree
}
procedure TfrmSQLhelp.findKeywordInTree;
var
tnode : TTreeNode;
i : Integer;
tmp : Boolean;
begin
if Assigned(treeTopics.Selected) and (treeTopics.Selected.Text = FKeyword) then begin
// We've come here after user selected a tree node via mouse. No need to search for it.
// Topic selected
VT := Sender as TVirtualStringTree;
if not Assigned(VT.FocusedNode) then
Exit;
end;
i := 0;
while i < treeTopics.Items.Count do
begin
tnode := treeTopics.Items[i];
inc(i);
if tnode.Text = FKeyword then
begin
tnode.MakeVisible;
treeTopics.Selected := tnode;
break;
end;
treeTopicsExpanding( self, tnode, tmp );
end;
treeTopics.SetFocus;
end;
{***
Selected item in treeTopics has changed
}
procedure TfrmSQLhelp.treeTopicsChange(Sender: TObject; Node: TTreeNode);
procedure OpenFolderIcons( ANode: TTreeNode );
begin
if ANode = nil then
exit;
if ANode.ImageIndex = ICONINDEX_CATEGORY_CLOSED then
begin
ANode.ImageIndex := ICONINDEX_CATEGORY_OPENED;
end;
// Recursively update ANode's parent node
OpenFolderIcons( ANode.Parent );
end;
var
i : Integer;
tNode : TTreeNode;
begin
// 1. Show corresponding help-text
if Node.ImageIndex = ICONINDEX_HELPITEM then
Keyword := Node.Text;
// 2. Ensure the icons in the preceding tree-path of the selected item
// show opened folders on each level
i := 0;
while i < treeTopics.Items.Count do
begin
tNode := treeTopics.Items[i];
if tNode.ImageIndex = ICONINDEX_CATEGORY_OPENED then
begin
tNode.ImageIndex := ICONINDEX_CATEGORY_CLOSED;
end;
inc(i);
end;
OpenFolderIcons( Node );
end;
{***
Get topics from category
}
procedure TfrmSQLhelp.treeTopicsExpanding(Sender: TObject; Node: TTreeNode;
var AllowExpansion: Boolean);
begin
if (Node.getFirstChild <> nil) and (Node.getFirstChild.Text = DUMMY_NODE_TEXT) then
begin
fillTreeLevel( Node );
end;
end;
{***
Fetch and show text of help-item in synmemo's
@return boolean Was the keyword found?
}
function TfrmSQLhelp.ShowHelpItem: Boolean;
var
Results: TMySQLQuery;
begin
lblKeyword.Caption := Copy(Keyword, 0, 100);
if VT.HasChildren[VT.FocusedNode] then
Exit;
FKeyword := VT.Text[VT.FocusedNode, VT.FocusedColumn];
lblKeyword.Caption := Copy(FKeyword, 0, 100);
MemoDescription.Lines.Clear;
MemoExample.Lines.Clear;
Caption := DEFAULT_WINDOW_CAPTION;
result := false; // Keyword not found yet
if Keyword <> '' then
try
if FKeyword <> '' then try
Screen.Cursor := crHourglass;
Results := Mainform.Connection.GetResults('HELP "'+lblKeyword.Caption+'"');
if Results.RecordCount = 1 then begin
// We found exactly one matching help item
lblKeyword.Caption := Results.Col('name');
FKeyword := lblKeyword.Caption;
if lblKeyword.Caption = '&' then
lblKeyword.Caption := '&&'; // Avoid displaying "_" as alt-hotkey
Caption := Caption + ' - ' + Keyword;
MemoDescription.Text := fixNewlines(Results.Col('description'));
MemoExample.Text := fixNewlines(Results.Col('example'));
result := true;
end;
Results := Mainform.Connection.GetResults('HELP '+esc(FKeyword));
Caption := Caption + ' - ' + FKeyword;
MemoDescription.Text := fixNewlines(Results.Col('description', True));
MemoExample.Text := fixNewlines(Results.Col('example', True));
finally
FreeAndNil(Results);
Screen.Cursor := crDefault;
end;
// Show the user if topic is (not) available
if memoDescription.Text = '' then
if memoDescription.GetTextLen = 0 then
memoDescription.Text := 'No help available for this keyword or no keyword was selected.';
if memoExample.Text = '' then
if memoExample.GetTextLen = 0 then
memoExample.Text := 'No example available or no keyword was selected.';
end;
procedure TfrmSQLhelp.treeTopicsFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
Results: PMySQLQuery;
begin
// Node gets destroyed - free memory used for bound SQL result
Results := Sender.GetNodeData(Node);
if Assigned(Results^) then
Results^.Free;
end;
procedure TfrmSQLhelp.treeTopicsGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: Integer);
begin
// Return open or closed book icon for folders, or document icon for topics
if not (Kind in [ikNormal, ikSelected]) then
Exit;
if Sender.HasChildren[Node] then begin
if Sender.Expanded[Node] then
ImageIndex := ICONINDEX_CATEGORY_OPENED
else
ImageIndex := ICONINDEX_CATEGORY_CLOSED;
end else
ImageIndex := ICONINDEX_HELPITEM;
end;
procedure TfrmSQLhelp.treeTopicsGetNodeDataSize(Sender: TBaseVirtualTree;
var NodeDataSize: Integer);
begin
// We bind one TMySQLQuery to a node
NodeDataSize := SizeOf(TMySQLQuery);
end;
function TfrmSQLhelp.GetHelpResult(Node: PVirtualNode): TMySQLQuery;
var
P: PMySQLQuery;
begin
// Find right result set for given node
if treeTopics.GetNodeLevel(Node) = 0 then
Result := FRootTopics
else begin
P := treeTopics.GetNodeData(Node.Parent);
Result := P^;
end;
end;
procedure TfrmSQLhelp.treeTopicsGetText(Sender: TBaseVirtualTree; Node: PVirtualNode;
Column: TColumnIndex; TextType: TVSTTextType; var CellText: string);
var
Results: TMySQLQuery;
begin
// Ask result set for node text
Results := GetHelpResult(Node);
Results.RecNo := Node.Index;
CellText := Results.Col('name');
end;
procedure TfrmSQLhelp.treeTopicsInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode;
var ChildCount: Cardinal);
var
Results: PMySQLQuery;
VT: TVirtualStringTree;
begin
// Return number of children for folder
VT := Sender as TVirtualStringTree;
Results := VT.GetNodeData(Node);
Results^ := MainForm.Connection.GetResults('HELP '+esc(VT.Text[Node, VT.Header.MainColumn]));
ChildCount := Results.RecordCount;
end;
procedure TfrmSQLhelp.treeTopicsInitNode(Sender: TBaseVirtualTree; ParentNode,
Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
Results: TMySQLQuery;
Folder: String;
begin
// Display plus button for nodes which are folders
Results := GetHelpResult(Node);
Results.RecNo := Node.Index;
Folder := '';
if Assigned(ParentNode) then
Folder := treeTopics.Text[ParentNode, treeTopics.Header.MainColumn];
if Results.ColExists('is_it_category')
and (Results.Col('is_it_category') = 'Y')
and (Results.Col('name') <> Folder)
then
Include(InitialStates, ivsHasChildren);
end;
{***
Save layout and close window
}
procedure TfrmSQLhelp.ButtonCloseClick(Sender: TObject);
begin
Close;
end;
procedure TfrmSQLhelp.FormDestroy(Sender: TObject);
begin
OpenRegistry;
MainReg.WriteInteger( REGNAME_SQLHELPWINLEFT, Left );
@ -282,81 +245,144 @@ begin
MainReg.WriteInteger( REGNAME_SQLHELPWINHEIGHT, Height );
MainReg.WriteInteger( REGNAME_SQLHELPPLWIDTH, pnlLeft.Width );
MainReg.WriteInteger( REGNAME_SQLHELPPRHEIGHT, memoDescription.Height );
Close;
end;
{***
Link/redirect to mysql.com for further help
@see http://www.heidisql.com/sqlhelp.php
}
procedure TfrmSQLhelp.ButtonOnlinehelpClick(Sender: TObject);
begin
// Link/redirect to mysql.com for further help
ShellExec( APPDOMAIN + 'sqlhelp.php?mysqlversion='+inttostr(Mainform.Connection.ServerVersionInt)+
'&keyword='+urlencode(keyword) );
end;
{***
Esc pressed - close form.
Seems that if we're in a memo, the ButtonClose.Cancel=True doesn't have an effect
}
procedure TfrmSQLhelp.memosKeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
procedure TfrmSQLhelp.memosKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
// Esc pressed - close form.
// Seems that if we're in a memo, the ButtonClose.Cancel=True doesn't have an effect
if Key = VK_ESCAPE then
ButtonCloseClick(self);
Close;
end;
procedure TfrmSQLhelp.SetKeyword(Value: string);
var
VT: TVirtualStringTree;
Node: PVirtualNode;
Results: TMySQLQuery;
SearchNoInit: Boolean;
begin
// Find keyword in tree
FKeyword := Value;
if ShowHelpItem then
findKeywordInTree;
if FKeyword = '' then
Exit;
Results := Mainform.Connection.GetResults('HELP '+esc(FKeyword));
while not Results.Eof do begin
if Results.Col('is_it_category', true) = 'N' then begin
FKeyword := Results.Col('name');
break;
end;
Results.Next;
end;
FreeAndNil(Results);
VT := treeTopics;
if (not Assigned(VT.FocusedNode)) // No node selected
or VT.HasChildren[VT.FocusedNode] // Selected node is a folder, not a document
or (VT.Text[VT.FocusedNode, VT.Header.MainColumn] <> FKeyword) // Node is not the right one
then begin
// Start searching in initialized nodes, to minimize number of "HELP xyz" queries in certain cases
Node := VT.GetFirst;
SearchNoInit := False;
while Assigned(Node) do begin
if (not VT.HasChildren[Node]) and (UpperCase(VT.Text[Node, VT.Header.MainColumn]) = UpperCase(FKeyword)) then begin
SelectNode(VT, Node);
break;
end;
if not SearchNoInit then begin
Node := VT.GetNextInitialized(Node);
if not Assigned(Node) then begin
SearchNoInit := True;
Node := VT.GetFirst;
end;
end;
if SearchNoInit then
Node := VT.GetNext(Node);
end;
end;
end;
procedure TfrmSQLhelp.editFilterChange(Sender: TObject);
var
tnode: TTreeNode;
Results: TMySQLQuery;
topic: String;
begin
// Apply filter text
if Trim(editFilter.Text) = '' then begin
fillTreeLevel(nil);
Exit;
end;
Keyword := editFilter.Text;
treeTopics.Items.Clear;
topic := Keyword;
try
Screen.Cursor := crHourglass;
Results := Mainform.Connection.GetResults('HELP "%'+topic+'%"');
while not Results.Eof do begin
tnode := treeTopics.Items.AddChild(nil, Results.Col('name'));
if Results.ColExists('is_it_category') and (Results.Col('is_it_category') = 'Y') then begin
tnode.ImageIndex := ICONINDEX_CATEGORY_CLOSED;
tnode.SelectedIndex := ICONINDEX_CATEGORY_OPENED;
// Add a dummy item to show the plus-button so the user sees that there this
// is a category. When the plus-button is clicked, fetch the content of the category
treeTopics.Items.AddChild(tnode, DUMMY_NODE_TEXT);
end else begin
tnode.ImageIndex := ICONINDEX_HELPITEM;
tnode.SelectedIndex := tnode.ImageIndex;
end;
Results.Next;
end;
finally
FreeAndNil(Results);
Screen.Cursor := crDefault;
end;
editFilter.SetFocus;
timerSearch.Enabled := False;
timerSearch.Enabled := True;
editFilter.RightButton.Visible := Trim(editFilter.Text) <> '';
end;
procedure TfrmSQLhelp.editFilterRightButtonClick(Sender: TObject);
begin
editFilter.Clear;
end;
procedure TfrmSQLhelp.DoSearch(Sender: TObject);
var
Node: PVirtualNode;
Search: String;
Vis: Boolean;
function HasVisibleChildItems(Node: PVirtualNode): Boolean;
var
N: PVirtualNode;
begin
N := treeTopics.GetFirstChild(Node);
Result := False;
while Assigned(N) do begin
if treeTopics.HasChildren[N] then
Result := HasVisibleChildItems(N)
else
Result := treeTopics.IsVisible[N];
if Result then
Exit;
N := treeTopics.GetNextSibling(N);
end;
end;
begin
// Apply filter text
Screen.Cursor := crHourglass;
treeTopics.BeginUpdate;
timerSearch.Enabled := False;
Search := Trim(editFilter.Text);
if Search = '' then begin
// Show all items
Node := treeTopics.GetFirstInitialized;
while Assigned(Node) do begin
treeTopics.IsVisible[Node] := True;
Node := treeTopics.GetNextInitialized(Node);
end;
end else begin
// Hide non matching child items
Node := treeTopics.GetFirst;
while Assigned(Node) do begin
if not treeTopics.HasChildren[Node] then
treeTopics.IsVisible[Node] := Pos(UpperCase(Search), UpperCase(treeTopics.Text[Node, treeTopics.Header.MainColumn])) > 0;
Node := treeTopics.GetNext(Node);
end;
// Hide empty folders
Node := treeTopics.GetFirst;
while Assigned(Node) do begin
if treeTopics.HasChildren[Node] then begin
Vis := HasVisibleChildItems(Node);
treeTopics.Expanded[Node] := Vis;
treeTopics.IsVisible[Node] := Vis;
end;
Node := treeTopics.GetNext(Node);
end;
end;
treeTopics.EndUpdate;
Screen.Cursor := crDefault;
end;
end.