lazarus/lcl/lazhelphtml.pas

460 lines
15 KiB
ObjectPascal

{
*****************************************************************************
This file is part of the Lazarus Component Library (LCL)
See the file COPYING.modifiedLGPL.txt, included in this distribution,
for details about the license.
*****************************************************************************
Author: Mattias Gaertner
Abstract:
Methods and types for simple HTML help.
}
unit LazHelpHTML;
{$mode objfpc}{$H+}
interface
uses
{$IFDEF MSWindows}Windows, ShellApi,{$ENDIF} // needed for ShellExecute, not good for WinCE, issue #36558
Classes, SysUtils,
// LazUtils
LazFileUtils, UTF8Process, LazStringUtils, LazConfigStorage, LazLoggerBase,
// LCL
LCLIntf, LCLStrConsts, HelpIntfs, LazHelpIntf;
type
{ THTMLHelpDatabase
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:
Put a THTMLHelpDatabase on a form.
Set AutoRegister to true.
Set KeywordPrefix to 'MyHelp/'
Set BaseURL to 'file://'
Put a THTMLBrowserHelpViewer on the form.
Set AutoRegister to true.
Set BrowserPath to '/usr/bin/mozilla'
Put a TEdit on a form.
Set HelpType to htKeyword
Set HelpKeyword to 'MyHelp/page.html'
Run the program.
Focus the edit field and press F1. The page 'page.html' will be shown.
}
THTMLHelpDatabase = class(THelpDatabase)
private
FBaseURL: string;
FDefaultBaseURL: string;
FKeywordPrefix: string;
FKeywordPrefixNode: THelpNode;
function IsBaseURLStored: boolean;
procedure SetBaseURL(const AValue: string);
procedure SetBuiltInBaseURL(const AValue: string);
procedure SetDefaultBaseURL(const AValue: string);
public
constructor Create(TheOwner: TComponent); override;
destructor Destroy; override;
function ShowURL(const URL, Title: string;
var ErrMsg: string): TShowHelpResult; virtual;
function ShowHelp(Query: THelpQuery; BaseNode, NewNode: THelpNode;
QueryItem: THelpQueryItem;
var ErrMsg: string): TShowHelpResult; override;
function GetNodesForKeyword(const HelpKeyword: string;
var ListOfNodes: THelpNodeQueryList;
var ErrMsg: string): TShowHelpResult; override;
function GetEffectiveBaseURL: string;
procedure Load(Storage: TConfigStorage); override;
procedure Save(Storage: TConfigStorage); override;
property DefaultBaseURL: string read FDefaultBaseURL write SetDefaultBaseURL;// used, if BaseURL is empty
published
property BuiltInBaseURL: string read FDefaultBaseURL write SetBuiltInBaseURL;// read only, shown in the IDE help options
property BaseURL: string read FBaseURL write SetBaseURL stored IsBaseURLStored;
property AutoRegister;
property KeywordPrefix: string read FKeywordPrefix write FKeywordPrefix;// see above
end;
{ THTMLBrowserHelpViewer
If no browser is specified it searches for a common browser. }
TOnFindDefaultBrowser = procedure(var DefaultBrowser, Params: string) of object;
THTMLBrowserHelpViewer = class(THelpViewer)
private
FBrowserParams: string;
FBrowserPath: string;
FDefaultBrowser: string;
FDefaultBrowserParams: string;
FOnFindDefaultBrowser: TOnFindDefaultBrowser;
procedure SetBrowserParams(const AValue: string);
procedure SetBrowserPath(const AValue: string);
public
constructor Create(TheOwner: TComponent); override;
function ShowNode(Node: THelpNode; var ErrMsg: string): TShowHelpResult; override;
procedure FindDefaultBrowser(out Browser, Params: string); virtual;
procedure Assign(Source: TPersistent); override;
procedure Load(Storage: TConfigStorage); override;
procedure Save(Storage: TConfigStorage); override;
function GetLocalizedName: string; override;
property OnFindDefaultBrowser: TOnFindDefaultBrowser
read FOnFindDefaultBrowser write FOnFindDefaultBrowser;
published
property BrowserPath: string read FBrowserPath write SetBrowserPath;
property BrowserParams: string read FBrowserParams write SetBrowserParams;
property AutoRegister;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('System',[THTMLHelpDatabase,THTMLBrowserHelpViewer]);
end;
{ THTMLHelpDatabase }
procedure THTMLHelpDatabase.SetBaseURL(const AValue: string);
begin
if FBaseURL=AValue then exit;
//debugln('THTMLHelpDatabase.SetBaseURL ',dbgsName(Self),' ',AValue);
if AValue=DefaultBaseURL then
FBaseURL:=''
else
FBaseURL:=AValue;
end;
procedure THTMLHelpDatabase.SetBuiltInBaseURL(const AValue: string);
begin
if AValue=BuiltInBaseURL then exit;
raise Exception.Create(rsTheBuiltInURLIsReadOnlyChangeTheBaseURLInstead);
end;
procedure THTMLHelpDatabase.SetDefaultBaseURL(const AValue: string);
begin
if FDefaultBaseURL=AValue then exit;
if (FBaseURL='') or (FBaseURL=FDefaultBaseURL) then
FBaseURL:=FDefaultBaseURL;
FDefaultBaseURL:=AValue;
end;
function THTMLHelpDatabase.IsBaseURLStored: boolean;
begin
Result:=FBaseURL<>DefaultBaseURL;
end;
constructor THTMLHelpDatabase.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
AddSupportedMimeType('text/html');
end;
destructor THTMLHelpDatabase.Destroy;
begin
FreeAndNil(FKeywordPrefixNode);
inherited Destroy;
end;
function THTMLHelpDatabase.ShowURL(const URL, Title: string; var ErrMsg: string
): TShowHelpResult;
var
URLType, URLPath, URLParams: string;
BaseURLType, BaseURLPath, BaseURLParams: string;
Viewer: THelpViewer;
EffBaseURL: String;
Node: THelpNode;
FullURL: String;
begin
//DebugLn('THTMLHelpDatabase.ShowURL A URL="',URL,'" Title="',Title,'"');
// find HTML viewer
Result:=FindViewer('text/html',ErrMsg,Viewer);
if Result<>shrSuccess then exit;
// make URL absolute
SplitURL(URL,URLType,URLPath,URLParams);
//debugln('THTMLHelpDatabase.ShowURL A NewNode.URL=',URL,' URLType=',URLType,' URLPath=',URLPath,' URLParams=',URLParams);
if URLType='file' then begin
if not URLFilenameIsAbsolute(URLPath) then begin
EffBaseURL:=GetEffectiveBaseURL;
//DebugLn('THTMLHelpDatabase.ShowURL file relative, making absolute... EffBaseURL="',EffBaseURL,'"');
if EffBaseURL<>'' then begin
SplitURL(EffBaseURL,BaseURLType,BaseURLPath,BaseURLParams);
if (BaseURLPath<>'') then
URLPath:=BaseURLPath+URLPath;
URLType:=BaseURLType;
end;
end;
if (URLType='file') and (not URLFilenameIsAbsolute(URLPath)) then
URLPath:=FilenameToURLPath(TrimFilename(GetCurrentDirUTF8+PathDelim))+URLPath;
if (URLType='file') and (not FileExistsUTF8(URLPath)) then begin
Result:=shrContextNotFound;
ErrMsg:=Format(hhsHelpTheHelpDatabaseWasUnableToFindFile, [ID, URLPath]);
exit;
end;
end;
FullURL:=CombineURL(URLType,URLPath,URLParams);
{$IFNDEF DisableChecks}
debugln('THTMLHelpDatabase.ShowURL B URL=',URL,' URLType=',URLType,' URLPath=',URLPath,' URLParams=',URLParams);
{$ENDIF}
// call viewer
Node:=nil;
try
Node:=THelpNode.CreateURL(Self,Title,FullURL);
Result:=Viewer.ShowNode(Node,ErrMsg);
finally
Node.Free;
end;
end;
function THTMLHelpDatabase.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:='THTMLHelpDatabase.ShowHelp Node.URLValid=false Node.URL="'+NewNode.URL+'"';
end;
end;
function THTMLHelpDatabase.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 FKeywordPrefixNode=nil then
FKeywordPrefixNode:=THelpNode.CreateURL(Self,'','');
Path:=copy(HelpKeyword,length(KeywordPrefix)+1,length(HelpKeyword));
FKeywordPrefixNode.Title:='Show page '+Path;
FKeywordPrefixNode.URL:='file://'+Path;
CreateNodeQueryListAndAdd(FKeywordPrefixNode,nil,ListOfNodes,true);
end;
end;
function THTMLHelpDatabase.GetEffectiveBaseURL: string;
begin
Result:='';
if BaseURL<>'' then begin
Result:=BaseURL;
if (Databases<>nil) then begin
Databases.SubstituteMacros(Result);
Result:=FilenameToURLPath(Result);
end;
//debugln('THTMLHelpDatabase.GetEffectiveBaseURL using BaseURL="',Result,'"');
end else if (BasePathObject<>nil) and (Databases<>nil) then begin
Result:=Databases.GetBaseURLForBasePathObject(BasePathObject);
//debugln('THTMLHelpDatabase.GetEffectiveBaseURL using BasePathObject="',Result,'"');
end;
if (Result='') and (DefaultBaseURL<>'') then begin
Result:=DefaultBaseURL;
if (Databases<>nil) then begin
Databases.SubstituteMacros(Result);
Result:=FilenameToURLPath(Result);
end;
//debugln('THTMLHelpDatabase.GetEffectiveBaseURL using DefaultBaseURL="',Result,'"');
end;
Result:=AppendURLPathDelim(Result);
end;
procedure THTMLHelpDatabase.Load(Storage: TConfigStorage);
begin
inherited Load(Storage);
BaseURL:=Storage.GetValue('BaseURL/Value',DefaultBaseURL);
end;
procedure THTMLHelpDatabase.Save(Storage: TConfigStorage);
begin
inherited Save(Storage);
Storage.SetDeleteValue('BaseURL/Value',BaseURL,DefaultBaseURL);
end;
{ THTMLBrowserHelpViewer }
procedure THTMLBrowserHelpViewer.SetBrowserParams(const AValue: string);
begin
if FBrowserParams=AValue then exit;
FBrowserParams:=AValue;
end;
procedure THTMLBrowserHelpViewer.SetBrowserPath(const AValue: string);
begin
if FBrowserPath=AValue then exit;
FBrowserPath:=AValue;
end;
constructor THTMLBrowserHelpViewer.Create(TheOwner: TComponent);
begin
inherited Create(TheOwner);
AddSupportedMimeType('text/html');
FBrowserParams:='%s';
ParameterHelp:=hhsHelpTheMacroSInBrowserParamsWillBeReplacedByTheURL;
end;
function THTMLBrowserHelpViewer.ShowNode(Node: THelpNode; var ErrMsg: string
): TShowHelpResult;
var
URLMacroPos: LongInt;
BrowserProcess: TProcessUTF8;
Executable, ParamsStr: String;
IsShellStr: Boolean = false;
begin
Result:=shrViewerError;
ErrMsg:='';
if (not Node.URLValid) then begin
ErrMsg:='THTMLBrowserHelpViewer.ShowNode Node.URLValid=false';
exit;
end;
if (Node.URL='') then begin
ErrMsg:='THTMLBrowserHelpViewer.ShowNode Node.URL empty';
exit;
end;
// check browser path
Executable:=BrowserPath;
ParamsStr:=BrowserParams;
if Executable='' then
FindDefaultBrowser(Executable, ParamsStr);
if Executable='' then begin
if (HelpDatabases<>nil)
and (CompareText(HelpDatabases.ClassName,'TIDEHelpDatabases')=0) then
ErrMsg:=Format(hhsHelpNoHTMLBrowserFoundPleaseDefineOne,[LineEnding])
else
ErrMsg:=hhsHelpNoHTMLBrowserFound;
exit;
end;
{$ifdef windows}
//The result of FindDefaultBrowser may or may not be quoted on Windows
//Since on Windows, a filename cannot contain a double quote, we simply remove them
//otherwise FileExistsUf8 and FileIsExecutable fail. Issue #0030502
if (Length(Executable) > 1) and (Executable[1] = '"') and (Executable[Length(Executable)] = '"') then
Executable := Copy(Executable, 2, Length(Executable)-2);
// Preparation of special handling for Microsoft Edge in Win10, issue #35659
IsShellStr := UpperCase(LeftStr(Executable,Pos(':',Executable)))='SHELL:';
{$endif windows}
if not IsShellStr then begin
if (not FileExistsUTF8(Executable)) then begin
ErrMsg:=Format(hhsHelpBrowserNotFound, [Executable]);
exit;
end;
if (not FileIsExecutable(Executable)) then begin
ErrMsg:=Format(hhsHelpBrowserNotExecutable, [Executable]);
exit;
end;
end;
//debugln('THTMLBrowserHelpViewer.ShowNode Node.URL=',Node.URL);
// create params and replace %ParamsStr for URL
URLMacroPos:=Pos('%s',ParamsStr);
if URLMacroPos>=1 then
ReplaceSubstring(ParamsStr,URLMacroPos,2,Node.URL)
else begin
if ParamsStr<>'' then
ParamsStr:=ParamsStr+' ';
ParamsStr:=ParamsStr+Node.URL;
end;
{$IFNDEF DisableChecks}
debugln('THTMLBrowserHelpViewer.ShowNode Executable="',Executable,'" Params="',ParamsStr,'"');
{$ENDIF}
// run
{$IFDEF MSWindows} // not good for WinCE! Issue #36558.
// Special handling for Microsoft Edge in Win10, issue #35659
if IsShellStr then begin
if ShellExecute(0,'open',PChar(Executable),PChar(ParamsStr),'',SW_SHOWNORMAL)<=32 then
ErrMsg := Format(hhsHelpErrorWhileExecuting,[Executable+' ',ParamsStr, LineEnding, 'ShellExecute'])
else
Result := shrSuccess;
end else
{$ENDIF}
try
BrowserProcess:=TProcessUTF8.Create(nil);
try
BrowserProcess.InheritHandles:=false;
BrowserProcess.Executable:=Executable;
SplitCmdLineParams(ParamsStr,BrowserProcess.Parameters);
BrowserProcess.Execute;
finally
BrowserProcess.Free;
end;
Result:=shrSuccess;
except
on E: Exception do begin
ErrMsg:=Format(hhsHelpErrorWhileExecuting, [Executable+' '+ParamsStr, LineEnding, E.Message]);
end;
end;
end;
procedure THTMLBrowserHelpViewer.FindDefaultBrowser(out Browser, Params: string);
begin
if FDefaultBrowser='' then
begin
if Assigned(OnFindDefaultBrowser) then
OnFindDefaultBrowser(FDefaultBrowser, FDefaultBrowserParams);
end;
if FDefaultBrowser = '' then
LCLIntf.FindDefaultBrowser(FDefaultBrowser, FDefaultBrowserParams);
Browser := FDefaultBrowser;
Params := FDefaultBrowserParams;
//DebugLn('THTMLBrowserHelpViewer.FindDefaultBrowser Browser=',Browser,' Params=',Params);
end;
procedure THTMLBrowserHelpViewer.Assign(Source: TPersistent);
var
Viewer: THTMLBrowserHelpViewer;
begin
if Source is THTMLBrowserHelpViewer then begin
Viewer:=THTMLBrowserHelpViewer(Source);
BrowserPath:=Viewer.BrowserPath;
BrowserParams:=Viewer.BrowserParams;
end;
inherited Assign(Source);
end;
procedure THTMLBrowserHelpViewer.Load(Storage: TConfigStorage);
begin
BrowserPath:=Storage.GetValue('Browser/Path','');
BrowserParams:=Storage.GetValue('Browser/Params','%s');
end;
procedure THTMLBrowserHelpViewer.Save(Storage: TConfigStorage);
begin
Storage.SetDeleteValue('Browser/Path',BrowserPath,'');
Storage.SetDeleteValue('Browser/Params',BrowserParams,'%s');
end;
function THTMLBrowserHelpViewer.GetLocalizedName: string;
begin
Result:='HTML Browser';
end;
end.