mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-02 04:43:40 +02:00
392 lines
11 KiB
ObjectPascal
392 lines
11 KiB
ObjectPascal
{
|
|
*****************************************************************************
|
|
See the file COPYING.modifiedLGPL.txt, included in this distribution,
|
|
for details about the license.
|
|
*****************************************************************************
|
|
|
|
Author: Mattias Gaertner
|
|
|
|
Abstract:
|
|
Methods and types for CHM help using chm viewer "lhelp".
|
|
}
|
|
unit LazHelpCHM;
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
{$IFDEF VerboseLCLHelp}
|
|
{$DEFINE VerboseChmHelp}
|
|
{$ENDIF}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, LazHelpIntf, LazConfigStorage, HelpIntfs,
|
|
Dialogs, Forms, LazLoggerBase, FileUtil, LazFileUtils, LHelpControl, LResources;
|
|
|
|
const
|
|
CHMMimeType = 'application/chm';
|
|
CHMPathParam = 'path';
|
|
|
|
type
|
|
{ TCHMHelpDatabase
|
|
|
|
KeywordPrefix: if set, then the database will handle all Keywords
|
|
beginning with this value. And when the path is created by replacing
|
|
the prefix with the BaseURL.
|
|
For example:
|
|
Create a chm. For example build and run chmmaker in lazarus/tools/chmmaker
|
|
to create the example.chm (lazarus/tools/chmmaker/example.chm).
|
|
|
|
Put a TCHMHelpDatabase on a form.
|
|
Set AutoRegister to true.
|
|
Set KeywordPrefix to 'example'
|
|
Set CHM file to '../../../tools/chmmaker/example.chm'
|
|
|
|
Put a TLHelpRemoteViewer on the form.
|
|
Set AutoRegister to true.
|
|
Set LHelpPath to the path of lhelp. E.g. '../../lhelp/lhelp'
|
|
|
|
Put a TEdit on a form.
|
|
Set HelpType to htKeyword
|
|
Set HelpKeyword to 'example/MainPage.html'
|
|
|
|
Run the program.
|
|
Focus the edit field and press F1. The page '/MainPage.html' will be shown.
|
|
Note: lhelp requires the leading slash.
|
|
}
|
|
TCHMHelpDatabase = class(THelpDatabase)
|
|
private
|
|
FFilename: string;
|
|
FHelpNode: THelpNode;
|
|
FKeywordPrefix: string;
|
|
procedure SetFilename(AValue: string);
|
|
procedure SetKeywordPrefix(AValue: string);
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function ShowHelp({%H-}Query: THelpQuery; {%H-}BaseNode, NewNode: THelpNode;
|
|
{%H-}QueryItem: THelpQueryItem;
|
|
var ErrMsg: string): TShowHelpResult; override;
|
|
function ShowURL(const URL, Title: string;
|
|
var ErrMsg: string): TShowHelpResult; virtual;
|
|
function GetNodesForKeyword(const HelpKeyword: string;
|
|
var ListOfNodes: THelpNodeQueryList;
|
|
var ErrMsg: string): TShowHelpResult; override;
|
|
procedure Load(Storage: TConfigStorage); override;
|
|
procedure Save(Storage: TConfigStorage); override;
|
|
published
|
|
property AutoRegister;
|
|
property Filename: string read FFilename write SetFilename;
|
|
property KeywordPrefix: string read FKeywordPrefix write SetKeywordPrefix;
|
|
end;
|
|
|
|
type
|
|
TOnFindLHelp = procedure(var Path: string) of object;
|
|
|
|
{ TLHelpConnector }
|
|
|
|
TLHelpConnector = class(THelpViewer)
|
|
private
|
|
FConnection: TLHelpConnection;
|
|
FLHelpPath: string;
|
|
FOnFindLHelp: TOnFindLHelp;
|
|
procedure SetLHelpPath(AValue: string);
|
|
public
|
|
constructor Create(TheOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
function ShowNode(Node: THelpNode; var ErrMsg: string): TShowHelpResult; override;
|
|
procedure Assign(Source: TPersistent); override;
|
|
procedure Load(Storage: TConfigStorage); override;
|
|
procedure Save(Storage: TConfigStorage); override;
|
|
function GetLocalizedName: string; override;
|
|
property OnFindLHelp: TOnFindLHelp read FOnFindLHelp write FOnFindLHelp;
|
|
property Connection: TLHelpConnection read FConnection;
|
|
published
|
|
property LHelpPath: string read FLHelpPath write SetLHelpPath;
|
|
property AutoRegister;
|
|
end;
|
|
|
|
procedure Register;
|
|
|
|
implementation
|
|
|
|
{$R lazhelpchm.res}
|
|
|
|
procedure Register;
|
|
begin
|
|
RegisterComponents('System',[TCHMHelpDatabase,TLHelpConnector]);
|
|
end;
|
|
|
|
{ TLHelpConnector }
|
|
|
|
procedure TLHelpConnector.SetLHelpPath(AValue: string);
|
|
begin
|
|
if FLHelpPath=AValue then Exit;
|
|
FLHelpPath:=AValue;
|
|
end;
|
|
|
|
constructor TLHelpConnector.Create(TheOwner: TComponent);
|
|
begin
|
|
inherited Create(TheOwner);
|
|
AddSupportedMimeType(CHMMimeType);
|
|
end;
|
|
|
|
destructor TLHelpConnector.Destroy;
|
|
begin
|
|
FConnection.Free;
|
|
inherited;
|
|
end;
|
|
|
|
function TLHelpConnector.ShowNode(Node: THelpNode; var ErrMsg: string
|
|
): TShowHelpResult;
|
|
var
|
|
Path: String;
|
|
IPCFile: String;
|
|
URLScheme: string;
|
|
URLPath: string;
|
|
URLParams: string;
|
|
CHMFilename: String;
|
|
SubPath: String;
|
|
Response: TLHelpResponse;
|
|
s: String;
|
|
begin
|
|
{$IFDEF VerboseChmHelp}
|
|
debugln(['TLHelpConnector.ShowNode START URL="',Node.URL,'"']);
|
|
{$ENDIF}
|
|
|
|
Result:=shrViewerError;
|
|
ErrMsg:='';
|
|
if (not Node.URLValid) then
|
|
begin
|
|
ErrMsg:='TLHelpConnector.ShowNode Node.URLValid=false';
|
|
exit;
|
|
end;
|
|
if (Node.URL='') then
|
|
begin
|
|
ErrMsg:='TLHelpConnector.ShowNode Node.URL empty';
|
|
exit;
|
|
end;
|
|
|
|
SplitURL(Node.URL,URLScheme,URLPath,URLParams);
|
|
CHMFilename:=CleanAndExpandFilename(URLPath);
|
|
if not FileExistsUTF8(CHMFilename) then
|
|
begin
|
|
ErrMsg:='chm file "'+CHMFilename+'" not found';
|
|
exit;
|
|
end;
|
|
if DirPathExists(CHMFilename) then
|
|
begin
|
|
ErrMsg:='invalid chm file "'+CHMFilename+'"';
|
|
exit;
|
|
end;
|
|
|
|
SubPath:='';
|
|
if (URLParams<>'') and (URLParams[1]='?') then
|
|
Delete(URLParams,1,1);
|
|
if LeftStr(URLParams,length(CHMPathParam)+1)=CHMPathParam+'=' then
|
|
begin
|
|
SubPath:=URLParams;
|
|
Delete(SubPath,1,length(CHMPathParam)+1);
|
|
end;
|
|
|
|
if Connection=nil then
|
|
begin
|
|
// create a connection to lhelp:
|
|
FConnection := TLHelpConnection.Create;
|
|
Connection.ProcessWhileWaiting := @Application.ProcessMessages;
|
|
end;
|
|
|
|
if Connection.ServerRunning = false then
|
|
begin
|
|
// Use '_lhlpctl_' in case application developer uses SimpleIPC
|
|
// and also uses the exe name followed by the process ID.
|
|
// See help protocol specs defined in
|
|
// http://wiki.lazarus.freepascal.org/Help_protocol
|
|
// Use process id in order to avoid conflicts when multiple entries are running
|
|
IPCFile:=LowerCase(ExtractFileName(Application.ExeName))+
|
|
'_lhlpctl_'+
|
|
copy(inttostr(GetProcessID)+'00000',1,5);
|
|
{$IFDEF Unix}
|
|
if FileExistsUTF8('/tmp/'+IPCFile) then
|
|
DeleteFileUTF8('/tmp/'+IPCFile);
|
|
{$ENDIF}
|
|
|
|
// get lhelp path
|
|
Path:=LHelpPath;
|
|
if Assigned(OnFindLHelp) then
|
|
OnFindLHelp(Path);
|
|
|
|
// append exe extension
|
|
if (ExtractFileExt(Path)='') and (GetExeExt<>'') then
|
|
Path:=Path+GetExeExt;
|
|
|
|
// search in Path
|
|
if (Path<>'') and (ExtractFilePath(Path)='') then
|
|
begin
|
|
s:=FindDefaultExecutablePath(Path);
|
|
if s<>'' then Path:=s;
|
|
end;
|
|
|
|
if not FileExistsUTF8(Path) then
|
|
begin
|
|
ErrMsg:='The chm viewer program lhelp was not found at "'+Path+'"';
|
|
exit;
|
|
end;
|
|
|
|
Connection.StartHelpServer(IPCFile,Path);
|
|
end;
|
|
|
|
{$IFDEF VerboseChmHelp}
|
|
debugln(['TLHelpConnector.ShowNode CHMFilename="',CHMFilename,'" SubPath="',SubPath,'"']);
|
|
{$ENDIF}
|
|
Response:=Connection.OpenURL(CHMFilename,SubPath);
|
|
case Response of
|
|
srSuccess: exit(shrSuccess);
|
|
srNoAnswer: ErrMsg:='lhelp does not respond';
|
|
srInvalidFile: ErrMsg:='lhelp can not open the file "'+CHMFilename+'"';
|
|
srInvalidURL,srInvalidContext: ErrMsg:='lhelp can not find the help entry "'+SubPath+'"';
|
|
else
|
|
ErrMsg:='Something is wrong with lhelp';
|
|
end;
|
|
debugln(['TLHelpConnector.ShowNode error: ',ErrMsg]);
|
|
end;
|
|
|
|
procedure TLHelpConnector.Assign(Source: TPersistent);
|
|
var
|
|
Src: TLHelpConnector;
|
|
begin
|
|
if Source is TLHelpConnector then
|
|
begin
|
|
Src:=TLHelpConnector(Source);
|
|
LHelpPath:=Src.LHelpPath;
|
|
end;
|
|
inherited Assign(Source);
|
|
end;
|
|
|
|
procedure TLHelpConnector.Load(Storage: TConfigStorage);
|
|
begin
|
|
inherited Load(Storage);
|
|
LHelpPath:=Storage.GetValue('LHelp/Path','');
|
|
end;
|
|
|
|
procedure TLHelpConnector.Save(Storage: TConfigStorage);
|
|
begin
|
|
inherited Save(Storage);
|
|
Storage.SetDeleteValue('LHelp/Path',LHelpPath,'');
|
|
end;
|
|
|
|
function TLHelpConnector.GetLocalizedName: string;
|
|
begin
|
|
Result:='LHelp Connector';
|
|
end;
|
|
|
|
{ TCHMHelpDatabase }
|
|
|
|
procedure TCHMHelpDatabase.SetFilename(AValue: string);
|
|
begin
|
|
if FFilename=AValue then Exit;
|
|
FFilename:=AValue;
|
|
end;
|
|
|
|
procedure TCHMHelpDatabase.SetKeywordPrefix(AValue: string);
|
|
begin
|
|
if FKeywordPrefix=AValue then Exit;
|
|
FKeywordPrefix:=AValue;
|
|
end;
|
|
|
|
constructor TCHMHelpDatabase.Create(TheOwner: TComponent);
|
|
begin
|
|
inherited Create(TheOwner);
|
|
AddSupportedMimeType(CHMMimeType);
|
|
end;
|
|
|
|
destructor TCHMHelpDatabase.Destroy;
|
|
begin
|
|
FreeAndNil(FHelpNode);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
function TCHMHelpDatabase.ShowHelp(Query: THelpQuery; BaseNode,
|
|
NewNode: THelpNode; QueryItem: THelpQueryItem; var ErrMsg: string
|
|
): TShowHelpResult;
|
|
begin
|
|
ErrMsg:='';
|
|
Result:=shrContextNotFound;
|
|
if NewNode.URLValid then
|
|
begin
|
|
Result:=ShowURL(NewNode.URL,NewNode.Title,ErrMsg);
|
|
end
|
|
else
|
|
begin
|
|
Result:=shrContextNotFound;
|
|
ErrMsg:='TCHMHelpDatabase.ShowHelp Node.URLValid=false Node.URL="'+NewNode.URL+'"';
|
|
end;
|
|
end;
|
|
|
|
function TCHMHelpDatabase.ShowURL(const URL, Title: string; var ErrMsg: string
|
|
): TShowHelpResult;
|
|
var
|
|
Viewer: THelpViewer;
|
|
Node: THelpNode;
|
|
begin
|
|
//DebugLn('TCHMHelpDatabase.ShowURL A URL="',URL,'" Title="',Title,'"');
|
|
|
|
if not FileExistsUTF8(Filename) then
|
|
begin
|
|
ErrMsg:='chm help file "'+Filename+'" not found';
|
|
exit(shrDatabaseNotFound);
|
|
end;
|
|
|
|
// find HTML viewer
|
|
Result:=FindViewer(CHMMimeType,ErrMsg,Viewer);
|
|
if Result<>shrSuccess then exit;
|
|
|
|
// call viewer
|
|
Node:=nil;
|
|
try
|
|
Node:=THelpNode.CreateURL(Self,Title,URL);
|
|
Result:=Viewer.ShowNode(Node,ErrMsg);
|
|
finally
|
|
Node.Free;
|
|
end;
|
|
end;
|
|
|
|
function TCHMHelpDatabase.GetNodesForKeyword(const HelpKeyword: string;
|
|
var ListOfNodes: THelpNodeQueryList; var ErrMsg: string): TShowHelpResult;
|
|
var
|
|
Path: String;
|
|
begin
|
|
Result:=inherited GetNodesForKeyword(HelpKeyword, ListOfNodes, ErrMsg);
|
|
if Result<>shrSuccess then exit;
|
|
|
|
if not (csDesigning in ComponentState) and
|
|
(KeywordPrefix<>'') and
|
|
(LeftStr(HelpKeyword,length(KeywordPrefix))=KeywordPrefix) then
|
|
begin
|
|
// HelpKeyword starts with KeywordPrefix -> add default node
|
|
if FHelpNode=nil then
|
|
FHelpNode:=THelpNode.CreateURL(Self,'','');
|
|
Path:=copy(HelpKeyword,length(KeywordPrefix)+1,length(HelpKeyword));
|
|
FHelpNode.Title:='Show page '+Path+' of '+ExtractFileName(Filename);
|
|
FHelpNode.URL:='chmfile://'+FilenameToURLPath(Filename)+'?'+CHMPathParam+'='+Path;
|
|
CreateNodeQueryListAndAdd(FHelpNode,nil,ListOfNodes,true);
|
|
end;
|
|
end;
|
|
|
|
procedure TCHMHelpDatabase.Load(Storage: TConfigStorage);
|
|
begin
|
|
inherited Load(Storage);
|
|
KeywordPrefix:=Storage.GetValue('KeywordPrefix','');
|
|
Filename:=Storage.GetValue('Filename','');
|
|
end;
|
|
|
|
procedure TCHMHelpDatabase.Save(Storage: TConfigStorage);
|
|
begin
|
|
inherited Save(Storage);
|
|
Storage.SetDeleteValue('KeywordPrefix',KeywordPrefix,'');
|
|
Storage.SetDeleteValue('Filename',Filename, '');
|
|
end;
|
|
|
|
end.
|
|
|