ChmHelp: Improve LHelp and related packages. Issue #24743, patch from Reinier Olislagers

git-svn-id: trunk@42270 -
This commit is contained in:
juha 2013-08-03 08:04:18 +00:00
parent c93b75b24c
commit c80a38a73b
8 changed files with 269 additions and 94 deletions

View File

@ -5,9 +5,6 @@ unit chmcontentprovider;
{$Note Compiling lhelp with search support} {$Note Compiling lhelp with search support}
{$DEFINE CHM_SEARCH} {$DEFINE CHM_SEARCH}
//{$else}
//{$Note Compiling lhelp *without* search support since your fpc version is not new enough}
//{$endif}
{$if (fpc_version=2) and (fpc_release>4)} {$if (fpc_version=2) and (fpc_release>4)}
{$Note Compiling lhelp *with* binary index and toc support} {$Note Compiling lhelp *with* binary index and toc support}
{$DEFINE CHM_BINARY_INDEX_TOC} {$DEFINE CHM_BINARY_INDEX_TOC}
@ -15,7 +12,7 @@ unit chmcontentprovider;
{off $DEFINE CHM_DEBUG_TIME} {off $DEFINE CHM_DEBUG_TIME}
{off $DEFINE CHM_SEARCH}
interface interface
@ -540,11 +537,9 @@ begin
fStatusBar.SimpleText:= ''; fStatusBar.SimpleText:= '';
{$IFDEF CHM_DEBUG_TIME} {$IFDEF CHM_DEBUG_TIME}
writeln('Eind: ',FormatDateTime('hh:nn:ss.zzz', Now)); writeln('End: ',FormatDateTime('hh:nn:ss.zzz', Now));
{$ENDIF} {$ENDIF}
{$IFDEF CHM_SEARCH} {$IFDEF CHM_SEARCH}
i := 0; i := 0;
while (HasSearchIndex = False) and (i < fChms.Count) do while (HasSearchIndex = False) and (i < fChms.Count) do

View File

@ -1,4 +1,4 @@
<?xml version="1.0"?> <?xml version="1.0" encoding="UTF-8"?>
<CONFIG> <CONFIG>
<ProjectOptions> <ProjectOptions>
<Version Value="9"/> <Version Value="9"/>

View File

@ -1,5 +1,7 @@
{ Copyright (C) <2005> <Andrew Haines> lhelp.lpr { Copyright (C) <2005> <Andrew Haines> lhelp.lpr
Lhelp CHM help viewer application
This source is free software; you can redistribute it and/or modify it under This source is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free the terms of the GNU General Public License as published by the Free
Software Foundation; either version 2 of the License, or (at your option) Software Foundation; either version 2 of the License, or (at your option)
@ -21,12 +23,8 @@ program lhelp;
uses uses
Interfaces, // this includes the LCL widgetset Interfaces, // this includes the LCL widgetset
SysUtils, SysUtils, Classes, Controls, Dialogs, Forms,
Classes, SimpleIPC, TurboPowerIPro, chmpopup, lhelpcontrolpkg, lhelpcore;
Controls,
Dialogs,
Forms
{ add your units here }, SimpleIPC, TurboPowerIPro, chmpopup, lhelpcontrolpkg, lhelpcore;
var var
X: Integer; X: Integer;
@ -40,14 +38,15 @@ begin
if LowerCase(ParamStr(X)) = '--help' then if LowerCase(ParamStr(X)) = '--help' then
begin begin
S := TStringList.Create; S := TStringList.Create;
S.Add(' LHelp Options:'); S.Add(' LHelp options:');
S.Add(''); S.Add('');
S.Add(' Usage: lhelp [[filename] [--context id] [--ipcname lhelp-myapp]]'); S.Add(' Usage: lhelp [[filename] [--context id] [--hide] [--ipcname lhelp-myapp]]');
S.Add(''); S.Add('');
S.Add(' --help : Show this information'); S.Add(' --help : Show this information');
S.Add(' --hide : Start hidden but accept communications via IPC');
S.Add(' --context : Show the help information related'); S.Add(' --context : Show the help information related');
S.Add(' to this context'); S.Add(' to this context');
S.Add(' --ipcname : The name of the ipc server to listen on for'); S.Add(' --ipcname : The name of the IPC server to listen on for');
S.Add(' programs who wish to control the viewer'); S.Add(' programs who wish to control the viewer');
if TextRec(Output).Mode = fmClosed then if TextRec(Output).Mode = fmClosed then
@ -60,15 +59,22 @@ begin
end; end;
Application.CreateForm(THelpForm, HelpForm); Application.CreateForm(THelpForm, HelpForm);
Application.CreateForm(THelpPopupForm, HelpPopupForm); Application.CreateForm(THelpPopupForm, HelpPopupForm);
try try
Application.Run; Application.Run;
except except
// try to remove stale names pipes so that a new instance can use them // try to remove stale named pipes so that a new instance can use them
if IPCServer <> nil then if IPCServer <> nil then
try try
FreeAndNil(IPCServer); FreeAndNil(IPCServer);
except except
end; end;
if IPCClient <> nil then
try
FreeAndNil(IPCClient);
except
end;
end; end;
end. end.

View File

@ -5,7 +5,7 @@ object HelpForm: THelpForm
Width = 758 Width = 758
ActiveControl = Panel1 ActiveControl = Panel1
Caption = 'LHelp' Caption = 'LHelp'
ClientHeight = 515 ClientHeight = 516
ClientWidth = 758 ClientWidth = 758
Icon.Data = { Icon.Data = {
7E04000000000100010010100000010020006804000016000000280000001000 7E04000000000100010010100000010020006804000016000000280000001000
@ -52,7 +52,7 @@ object HelpForm: THelpForm
OnKeyUp = FormKeyUp OnKeyUp = FormKeyUp
OnShow = FormShow OnShow = FormShow
Position = poScreenCenter Position = poScreenCenter
LCLVersion = '0.9.31' LCLVersion = '1.1'
Visible = True Visible = True
object Panel1: TPanel object Panel1: TPanel
Left = 0 Left = 0
@ -105,7 +105,6 @@ object HelpForm: THelpForm
8B000000000000000000B07522006780280037221A005E392A00683B2C009129 8B000000000000000000B07522006780280037221A005E392A00683B2C009129
1B000000000038AB3E000000000068BC74000000000000000000 1B000000000038AB3E000000000068BC74000000000000000000
} }
NumGlyphs = 0
OnClick = ForwardToolBtnClick OnClick = ForwardToolBtnClick
end end
object BackBttn: TSpeedButton object BackBttn: TSpeedButton
@ -150,7 +149,6 @@ object HelpForm: THelpForm
8B00000000000000000053575500535755005659580000000000000000000000 8B00000000000000000053575500535755005659580000000000000000000000
0000000000000000000000000000000000000000000000000000 0000000000000000000000000000000000000000000000000000
} }
NumGlyphs = 0
OnClick = BackToolBtnClick OnClick = BackToolBtnClick
end end
object HomeBttn: TSpeedButton object HomeBttn: TSpeedButton
@ -235,13 +233,12 @@ object HelpForm: THelpForm
EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7 EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7
EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00 EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00E4E7EA00
} }
NumGlyphs = 0
OnClick = HomeToolBtnClick OnClick = HomeToolBtnClick
end end
end end
object PageControl: TPageControl object PageControl: TPageControl
Left = 0 Left = 0
Height = 483 Height = 484
Top = 32 Top = 32
Width = 758 Width = 758
Align = alClient Align = alClient

View File

@ -1,4 +1,6 @@
{ Copyright (C) 2005 Andrew Haines { Copyright (C) 2005-2013 Andrew Haines, Lazarus contributors
Main form for lhelp. Includes processing/data communication.
This source is free software; you can redistribute it and/or modify it under This source is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free the terms of the GNU General Public License as published by the Free
@ -95,27 +97,46 @@ type
private private
{ private declarations } { private declarations }
fServerName: String; fServerName: String;
// Receives commands from IDE
fInputIPC: TSimpleIPCServer; fInputIPC: TSimpleIPCServer;
// Sends responses back to IDE
// only used if lhelp started with --ipcname to indicate
// IPC communication method should be used
fOutputIPC: TSimpleIPCClient; fOutputIPC: TSimpleIPCClient;
fServerTimer: TTimer; fServerTimer: TTimer;
fContext: LongInt; // used once when we are started on the command line with --context fContext: LongInt; // used once when we are started on the command line with --context
fConfig: TXMLConfig; fConfig: TXMLConfig;
FHasShowed: Boolean; FHasShowed: Boolean;
fHide: boolean; //If yes, start with content hidden. Otherwise start normally
// Load preferences; separate preferences per coupled server/IDE
procedure LoadPreferences(AIPCName: String); procedure LoadPreferences(AIPCName: String);
procedure SavePreferences({%H-}AIPCName: String); // Saves preferences. Uses existing config loaded by LoadPreferences
procedure SavePreferences;
procedure AddRecentFile(AFileName: String); procedure AddRecentFile(AFileName: String);
procedure ContentTitleChange({%H-}sender: TObject); procedure ContentTitleChange({%H-}sender: TObject);
procedure OpenRecentItemClick(Sender: TObject); procedure OpenRecentItemClick(Sender: TObject);
// Send response back to server (IDE)
// Used to acknowledge commands from the server
procedure SendResponse(Response: DWord); procedure SendResponse(Response: DWord);
// Wait for message from server (IDE) and process
procedure ServerMessage(Sender: TObject); procedure ServerMessage(Sender: TObject);
// Parse any given command line options
procedure ReadCommandLineOptions; procedure ReadCommandLineOptions;
procedure StartServer(ServerName: String); // Start simple IPC server/client
procedure StopServer; // ServerName can be the variable passed to --ipcname
function OpenURL(const AURL: String; AContext: THelpContext=-1): DWord; // It is used both for starting up the local server and to ID the remote server
procedure StartComms(ServerName: String);
// Stop simple IPC server/client
procedure StopComms;
// Open specified URL in viewer window
function OpenURL(const AURL: String; AContext: THelpContext=-1): DWord;
// Open specified URL - presumably used to queue URLs for delayed opening
procedure LateOpenURL(Url: PStringItem); procedure LateOpenURL(Url: PStringItem);
function ActivePage: TContentTab; function ActivePage: TContentTab;
// Update UI visibility
procedure RefreshState; procedure RefreshState;
procedure ShowError(AError: String); procedure ShowError(AError: String);
// Set keyup handler for control (and any child controls)
procedure SetKeyUp(AControl: TControl); procedure SetKeyUp(AControl: TControl);
public public
{ public declarations } { public declarations }
@ -124,8 +145,12 @@ type
var var
HelpForm: THelpForm; HelpForm: THelpForm;
IPCClient: TSimpleIPCClient;
IPCServer: TSimpleIPCServer; IPCServer: TSimpleIPCServer;
const INVALID_FILE_TYPE = 1;
const
INVALID_FILE_TYPE = 1;
VERSION_STAMP = '2013-07-31'; //used in displaying about form etc
implementation implementation
@ -160,14 +185,15 @@ begin
f.BorderStyle := bsDialog; f.BorderStyle := bsDialog;
f.Position := poMainFormCenter; f.Position := poMainFormCenter;
f.Constraints.MinWidth := 150; f.Constraints.MinWidth := 150;
f.Constraints.MaxWidth := 250; f.Constraints.MaxWidth := 350;
l := TLabel.Create(f); l := TLabel.Create(f);
l.Parent := f;; l.Parent := f;;
l.Align := alTop; l.Align := alTop;
l.BorderSpacing.Around := 6; l.BorderSpacing.Around := 6;
l.Caption := 'LHelp (CHM file viewer)' + LineEnding + l.Caption := 'LHelp (CHM file viewer)' + LineEnding +
'Ver. 2009.06.08' + LineEnding + 'Version ' + VERSION_STAMP + LineEnding +
'Copyright (C) Andrew Haines'; 'Copyright (C) Andrew Haines, ' + LineEnding +
'Lazarus contributors';
l.AutoSize := True; l.AutoSize := True;
l.WordWrap := True; l.WordWrap := True;
b := TButton.Create(f); b := TButton.Create(f);
@ -236,7 +262,7 @@ begin
URLSAllowed := Trim(URLSALLowed); URLSAllowed := Trim(URLSALLowed);
fRes:=''; fRes:='';
if InputQuery('Please Enter a URL', 'Supported URL type(s): (' +URLSAllowed+ ')', fRes) then if InputQuery('Please enter a URL', 'Supported URL type(s): (' +URLSAllowed+ ')', fRes) then
begin begin
if OpenURL(fRes) = ord(srSuccess) then if OpenURL(fRes) = ord(srSuccess) then
AddRecentFile(fRes); AddRecentFile(fRes);
@ -249,17 +275,19 @@ begin
Visible:= False; Visible:= False;
Application.ProcessMessages; Application.ProcessMessages;
FileMenuCloseItemClick(Sender); FileMenuCloseItemClick(Sender);
StopServer; StopComms;
SavePreferences(fServerName); SavePreferences;
end; end;
procedure THelpForm.FormCreate(Sender: TObject); procedure THelpForm.FormCreate(Sender: TObject);
begin begin
fContext := -1; fContext := -1;
fHide := false;
ReadCommandLineOptions; ReadCommandLineOptions;
LoadPreferences(fServerName); LoadPreferences(fServerName);
// Only start IPC if server name passed in --ipcname
if fServerName <> '' then begin if fServerName <> '' then begin
StartServer(fServerName); StartComms(fServerName);
end; end;
RefreshState; RefreshState;
SetKeyUp(Self); SetKeyUp(Self);
@ -279,7 +307,6 @@ begin
if FHasShowed then if FHasShowed then
Exit; Exit;
FHasShowed := True; FHasShowed := True;
end; end;
procedure THelpForm.ForwardToolBtnClick(Sender: TObject); procedure THelpForm.ForwardToolBtnClick(Sender: TObject);
@ -319,11 +346,16 @@ procedure THelpForm.LoadPreferences(AIPCName: String);
var var
PrefFile: String; PrefFile: String;
RecentCount: Integer; RecentCount: Integer;
ServerPart: String;
i: Integer; i: Integer;
begin begin
PrefFile := GetAppConfigDirUTF8(False); PrefFile := GetAppConfigDirUTF8(False);
ForceDirectoriesUTF8(PrefFile); ForceDirectoriesUTF8(PrefFile);
PrefFile:=Format('%slhelp-%s.conf',[IncludeTrailingPathDelimiter(PrefFile), AIPCName]); // --ipcname passes a server ID that consists of a
// a server-dependent constant together with a process ID.
// Strip out the process ID to get fixed config file names for one server
ServerPart := Copy(AIPCName, 1, length(AIPCName)-5); //strip out PID
PrefFile:=Format('%slhelp-%s.conf',[IncludeTrailingPathDelimiter(PrefFile), ServerPart]);
fConfig := TXMLConfig.Create(Self); fConfig := TXMLConfig.Create(Self);
fConfig.Filename:=PrefFile; fConfig.Filename:=PrefFile;
@ -333,6 +365,9 @@ begin
Width := fConfig.GetValue('Position/Width/Value', Width); Width := fConfig.GetValue('Position/Width/Value', Width);
Height := fConfig.GetValue('Position/Height/Value', Height); Height := fConfig.GetValue('Position/Height/Value', Height);
if fConfig.GetValue('Position/Maximized',false)=true then
Windowstate:=wsMaximized;
OpenDialog1.FileName := fConfig.GetValue('LastFileOpen/Value', OpenDialog1.FileName); OpenDialog1.FileName := fConfig.GetValue('LastFileOpen/Value', OpenDialog1.FileName);
RecentCount:= fConfig.GetValue('Recent/ItemCount/Value', 0); RecentCount:= fConfig.GetValue('Recent/ItemCount/Value', 0);
@ -341,7 +376,7 @@ begin
AddRecentFile(fConfig.GetValue('Recent/Item'+IntToStr(i)+'/Value','')); AddRecentFile(fConfig.GetValue('Recent/Item'+IntToStr(i)+'/Value',''));
end; end;
procedure THelpForm.SavePreferences(AIPCName: String); procedure THelpForm.SavePreferences;
var var
i: Integer; i: Integer;
begin begin
@ -351,6 +386,10 @@ begin
fConfig.SetValue('Position/Top/Value', Top); fConfig.SetValue('Position/Top/Value', Top);
fConfig.SetValue('Position/Width/Value', Width); fConfig.SetValue('Position/Width/Value', Width);
fConfig.SetValue('Position/Height/Value', Height); fConfig.SetValue('Position/Height/Value', Height);
end
else
begin
fConfig.SetValue('Position/Maximized', true);
end; end;
fConfig.SetValue('LastFileOpen/Value', OpenDialog1.FileName); fConfig.SetValue('LastFileOpen/Value', OpenDialog1.FileName);
@ -412,31 +451,27 @@ procedure THelpForm.SendResponse(Response: DWord);
var var
Stream: TMemoryStream; Stream: TMemoryStream;
begin begin
fOutputIPC := TSimpleIPCClient.Create(nil);
fOutputIPC.ServerID := fServerName+'client';
if fOutputIPC.ServerRunning {$IFDEF STALE_PIPE_WORKAROUND} and not IPCPipeIsStale(fOutputIPC){$ENDIF}
then
fOutputIPC.Active := True;
Stream := TMemoryStream.Create; Stream := TMemoryStream.Create;
Stream.WriteDWord(Response); try
Stream.WriteDWord(Response);
if fOutputIPC.Active then if assigned(fOutputIPC) and fOutputIPC.Active then
fOutputIPC.SendMessage(mtUnknown, Stream); fOutputIPC.SendMessage(mtUnknown, Stream);
finally
if fOutputIPC.Active then Stream.Free;
fOutputIPC.Active := False; end;
FreeAndNil(fOutputIPC);
end; end;
procedure THelpForm.ServerMessage(Sender: TObject); procedure THelpForm.ServerMessage(Sender: TObject);
var var
UrlReq: TUrlRequest; UrlReq: TUrlRequest;
FileReq:TFileRequest; FileReq: TFileRequest;
ConReq: TContextRequest; ConReq: TContextRequest;
MiscReq: TMiscRequest;
MustClose: boolean=false;
Stream: TStream; Stream: TStream;
Res: LongWord; Res: LongWord;
Url: String; Url: String='';
begin begin
if fInputIPC.PeekMessage(5, True) then begin if fInputIPC.PeekMessage(5, True) then begin
Stream := fInputIPC.MsgData; Stream := fInputIPC.MsgData;
@ -470,13 +505,48 @@ begin
Url := 'file://'+FileReq.FileName; Url := 'file://'+FileReq.FileName;
Res := OpenURL(Url, ConReq.HelpContext); Res := OpenURL(Url, ConReq.HelpContext);
end; end;
rtMisc : begin
Stream.Position := 0;
FillByte(MiscReq{%H-},SizeOf(MiscReq),0);
Stream.Read(MiscReq, SizeOf(MiscReq));
case MiscReq.RequestID of
mrClose:
begin
MustClose:=true;
Res:= ord(srSuccess);
end;
mrShow:
begin
fHide := false;
PageControl.Visible:=true;
Res := ord(srSuccess);
end;
mrVersion:
begin
//Protocol version encoded in the filename
// Verify what we support
if strtointdef(FileReq.FileName,0)=strtointdef(PROTOCOL_VERSION,0) then
Res := ord(srSuccess)
else
Res := ord(srError); //version not supported
end
else {Unknown}
Res := ord(srUnknown);
end;
end;
end; end;
if Res = Ord(srSuccess) then if (URL<>'') and (Res = Ord(srSuccess)) then
AddRecentFile(Url); AddRecentFile(Url);
SendResponse(Res); SendResponse(Res);
Self.SendToBack; if MustClose then
Self.BringToFront; Application.Terminate;
Self.ShowOnTop;
if (MustClose=false) and (fHide=false) then
begin
Self.SendToBack;
Self.BringToFront;
Self.ShowOnTop;
end;
end; end;
end; end;
@ -499,7 +569,7 @@ begin
IsHandled[X] := True; IsHandled[X] := True;
inc(X); inc(X);
end; end;
end else if LowerCase(ParamStrUTF8(X)) = '--context' then begin end else if LowerCase(ParamStrUTF8(X)) = '--context' then begin
IsHandled[X] := True; IsHandled[X] := True;
inc(X); inc(X);
if (X <= ParamCount) then if (X <= ParamCount) then
@ -507,11 +577,16 @@ begin
IsHandled[X] := True; IsHandled[X] := True;
inc(X); inc(X);
end; end;
end else if LowerCase(ParamStrUTF8(X)) = '--hide' then begin
IsHandled[X] := True;
inc(X);
fHide:=true;
end else begin end else begin
IsHandled[X]:=copy(ParamStrUTF8(X),1,1)='-'; // ignore other parameters IsHandled[X]:=copy(ParamStrUTF8(X),1,1)='-'; // ignore other parameters
inc(X); inc(X);
end; end;
end; end;
// Loop through a second time for the url // Loop through a second time for the url
for X := 1 to ParamCount do for X := 1 to ParamCount do
if not IsHandled[X] then begin if not IsHandled[X] then begin
@ -535,9 +610,13 @@ begin
end; end;
end; end;
procedure THelpForm.StartServer(ServerName: String); procedure THelpForm.StartComms(ServerName: String);
begin // Starts IPC server and client for two-way communication with
// controlling program (e.g. Lazarus IDE).
// Only useful if IPC serverID is passed through the --ipcname
// command.
begin
fInputIPC := TSimpleIPCServer.Create(nil); fInputIPC := TSimpleIPCServer.Create(nil);
fInputIPC.ServerID := ServerName; fInputIPC.ServerID := ServerName;
fInputIPC.Global := True; fInputIPC.Global := True;
@ -550,20 +629,38 @@ begin
fServerTimer.Enabled := True; fServerTimer.Enabled := True;
ServerMessage(nil); ServerMessage(nil);
fOutputIPC := TSimpleIPCClient.Create(nil);
fOutputIPC.ServerID := ServerName+'client';
try
if fOutputIPC.ServerRunning {$IFDEF STALE_PIPE_WORKAROUND} and not IPCPipeIsStale(fOutputIPC){$ENDIF}
then
fOutputIPC.Active := True;
except
fOutputIPC.Active := False;
end;
IPCClient := fOutputIPC;
end; end;
procedure THelpForm.StopServer; procedure THelpForm.StopComms;
begin begin
if fInputIPC = nil then if fInputIPC <> nil then
exit; begin
if fInputIPC.Active then
fInputIPC.Active := False;
if fInputIPC.Active then FreeAndNil(fInputIPC);
fInputIPC.Active := False; IPCServer := nil;
FreeAndNil(fServerTimer);
end;
FreeAndNil(fInputIPC); if fOutputIPC <> nil then
IPCServer := nil; begin
FreeAndNil(fServerTimer); if fOutputIPC.Active then
fOutputIPC.Active := False;
FreeAndNil(fOutputIPC);
IPCClient := nil;
end;
end; end;
function THelpForm.OpenURL(const AURL: String; AContext: THelpContext): DWord; function THelpForm.OpenURL(const AURL: String; AContext: THelpContext): DWord;
@ -598,7 +695,6 @@ begin
Exit; Exit;
end; end;
for I := 0 to PageControl.PageCount-1 do begin for I := 0 to PageControl.PageCount-1 do begin
if fRealContentProvider.ClassName = TContentTab(PageControl.Pages[I]).ContentProvider.ClassName then begin if fRealContentProvider.ClassName = TContentTab(PageControl.Pages[I]).ContentProvider.ClassName then begin
fPage := TContentTab(PageControl.Pages[I]); fPage := TContentTab(PageControl.Pages[I]);
@ -615,7 +711,7 @@ begin
if fPage = nil then if fPage = nil then
begin begin
//no page was found already to handle this content so create one //no existing page that can handle this content, so create one
fPage := TContentTab.Create(PageControl); fPage := TContentTab.Create(PageControl);
fPage.ContentProvider := fRealContentProvider.Create(fPage, ImageList1); fPage.ContentProvider := fRealContentProvider.Create(fPage, ImageList1);
fPAge.ContentProvider.OnTitleChange:=@ContentTitleChange; fPAge.ContentProvider.OnTitleChange:=@ContentTitleChange;
@ -634,7 +730,8 @@ begin
else else
Result := Ord(srInvalidFile); Result := Ord(srInvalidFile);
ShowOnTop; if not fHide then
ShowOnTop;
end; end;
@ -659,7 +756,25 @@ procedure THelpForm.RefreshState;
var var
en: Boolean; en: Boolean;
begin begin
en := Assigned(ActivePage); if fHide then
begin
en := false;
// Hide content page
// todo: even though this code perhaps will hide the content window,
// starting laz+pressing f1 will show content while loading all chms
if Assigned(ActivePage) then
with TChmContentProvider(ActivePage.ContentProvider) do
begin
ActivePage.Visible:=false;
Visible:=false;
//todo: are these necessary
TabsControl.Visible := false;
Splitter.Visible := false;
end;
end
else
en := Assigned(ActivePage);
BackBttn.Enabled := en; BackBttn.Enabled := en;
ForwardBttn.Enabled := en; ForwardBttn.Enabled := en;
HomeBttn.Enabled := en; HomeBttn.Enabled := en;
@ -685,7 +800,7 @@ begin
if (AControl = nil) or not (AControl.InheritsFrom(TWinControl)) then if (AControl = nil) or not (AControl.InheritsFrom(TWinControl)) then
Exit; Exit;
for i := 0 to WCont.ControlCount-1 do for i := 0 to WCont.ControlCount-1 do
SetKeyUp(WCont.Controls[i]); SetKeyUp(WCont.Controls[i]);
WCont.OnKeyUp:=@FormKeyUp; WCont.OnKeyUp:=@FormKeyUp;
end; end;
@ -705,6 +820,7 @@ end;
finalization finalization
if IPCServer <> nil then if IPCServer <> nil then
FreeAndNil(IPCServer); FreeAndNil(IPCServer);
if IPCClient <> nil then
FreeAndNil(IPCClient);
end. end.

View File

@ -7,7 +7,7 @@
Author: Mattias Gaertner Author: Mattias Gaertner
Abstract: Abstract:
Methods and types for simple CHM help using chm viewer "lhelp". Methods and types for CHM help using chm viewer "lhelp".
} }
unit LazHelpCHM; unit LazHelpCHM;

View File

@ -4,8 +4,7 @@ unit LHelpControl;
Starts, stops and controls external help viewer via IPC. Starts, stops and controls external help viewer via IPC.
This is used to display context-sensitive help in Lazarus, and could be used in applications to do the same. This is used to display context-sensitive help in Lazarus, and could be used in applications to do the same.
This unit serves as reference implementation and documentation of the protocol used to communicate with help viewers. Also contains definitions used by both Lazarus IDE and help viewers.
Currently, the only help viewer that supports this protocol is the lhelp CHM help viewer. Currently, the only help viewer that supports this protocol is the lhelp CHM help viewer.
} }
@ -25,12 +24,16 @@ uses
{$ENDIF} {$ENDIF}
Classes, SysUtils, FileUtil, LazLogger, SimpleIPC, process, UTF8Process; Classes, SysUtils, FileUtil, LazLogger, SimpleIPC, process, UTF8Process;
const
PROTOCOL_VERSION='1'; //IDE<>LHelp communication protocol version. Please update when breaking compatibility
type type
TRequestType = (rtFile, rtUrl, rtContext); TRequestType = (rtFile, rtUrl, rtContext, rtMisc {window handling etc});
TMiscRequests = (mrShow, mrVersion, mrClose);
TLHelpResponse = (srNoAnswer, srUnknown, srSuccess, srInvalidFile, srInvalidURL, srInvalidContext); TLHelpResponse = (srError, srNoAnswer, srUnknown, srSuccess, srInvalidFile, srInvalidURL, srInvalidContext);
TFileRequest = record TFileRequest = record
// Opening files
RequestType: TRequestType; RequestType: TRequestType;
FileName: array[0..512] of char; FileName: array[0..512] of char;
end; end;
@ -42,6 +45,11 @@ type
FileRequest: TFileRequest; FileRequest: TFileRequest;
HelpContext: THelpContext; HelpContext: THelpContext;
end; end;
TMiscRequest = record
// In this record, the FileName array may have a meaning specific to the request ID.
FileRequest: TFileRequest;
RequestID: TMiscRequests;
end;
TProcedureOfObject = procedure of object; TProcedureOfObject = procedure of object;
@ -52,22 +60,26 @@ type
FProcessWhileWaiting: TProcedureOfObject; FProcessWhileWaiting: TProcedureOfObject;
fServerOut: TSimpleIPCClient; // sends messages to lhelp fServerOut: TSimpleIPCClient; // sends messages to lhelp
fServerIn: TSimpleIPCServer; // recieves messages from lhelp fServerIn: TSimpleIPCServer; // recieves messages from lhelp
// Wait for help viewer to respond in a reasonable timeframe and return the response
function WaitForMsgResponse: TLHelpResponse; function WaitForMsgResponse: TLHelpResponse;
// Send a message to the help viewer
function SendMessage(Stream: TStream): TLHelpResponse; function SendMessage(Stream: TStream): TLHelpResponse;
public public
constructor Create; constructor Create;
destructor Destroy; override; destructor Destroy; override;
// Checks whether the server is running using SimpleIPC // Checks whether the server is running using SimpleIPC
function ServerRunning: Boolean; function ServerRunning: Boolean;
// Starts server // Starts remote server (help viewer); if Hide specified, asks the help server to hide itself/run minimized while starting
// Server must support a switch --ipcname that accepts the NameForServer argument to identify it for SimpleIPC // Server must support a switch --ipcname that accepts the NameForServer argument to identify it for SimpleIPC
function StartHelpServer(NameForServer: String; ServerEXE: String = ''): Boolean; function StartHelpServer(NameForServer: String; ServerEXE: String = '';Hide: boolean=false): Boolean;
// Shows URL in the HelpFileName file by sending a TUrlRequest // Shows URL in the HelpFileName file by sending a TUrlRequest
function OpenURL(HelpFileName: String; Url: String): TLHelpResponse; function OpenURL(HelpFileName: String; Url: String): TLHelpResponse;
// Shows help for Context in the HelpFileName file by sending a TContextRequest request // Shows help for Context in the HelpFileName file by sending a TContextRequest request
function OpenContext(HelpFileName: String; Context: THelpContext): TLHelpResponse; function OpenContext(HelpFileName: String; Context: THelpContext): TLHelpResponse;
// Opens HelpFileName by sending a TContextRequest // Opens HelpFileName by sending a TContextRequest
function OpenFile(HelpFileName: String): TLHelpResponse; function OpenFile(HelpFileName: String): TLHelpResponse;
// Requests to run command on viewer by sending a TMiscrequest
function RunMiscCommand(CommandID: TMiscRequests): TLHelpResponse;
property ProcessWhileWaiting: TProcedureOfObject read FProcessWhileWaiting write FProcessWhileWaiting; property ProcessWhileWaiting: TProcedureOfObject read FProcessWhileWaiting write FProcessWhileWaiting;
end; end;
@ -169,7 +181,7 @@ begin
end; end;
function TLHelpConnection.StartHelpServer(NameForServer: String; function TLHelpConnection.StartHelpServer(NameForServer: String;
ServerEXE: String): Boolean; ServerEXE: String; Hide: boolean=false): Boolean;
var var
X: Integer; X: Integer;
Cmd: String; Cmd: String;
@ -184,7 +196,8 @@ begin
fServerOut.Active := False; fServerOut.Active := False;
fServerOut.ServerID := NameForServer; fServerOut.ServerID := NameForServer;
if not ServerRunning then begin if not ServerRunning then begin
Cmd:= ServerExe + ' --ipcname ' + NameForServer; Cmd := ServerExe + ' --ipcname ' + NameForServer;
if Hide then Cmd := Cmd + ' --hide';
{$IFDEF darwin} {$IFDEF darwin}
if DirectoryExistsUTF8(ServerEXE+'.app') then if DirectoryExistsUTF8(ServerEXE+'.app') then
ServerEXE+='.app'; ServerEXE+='.app';
@ -265,5 +278,29 @@ begin
end; end;
end; end;
function TLHelpConnection.RunMiscCommand(CommandID: TMiscRequests): TLHelpResponse;
var
MiscRequest : TMiscRequest;
Stream: TMemoryStream;
begin
Stream := TMemoryStream.Create;
try
MiscRequest.FileRequest.RequestType := rtMisc;
MiscRequest.FileRequest.FileName := ''+#0; //i
//CommandID is ord(TMiscRequests)
MiscRequest.RequestID:=CommandID;
case CommandID of
mrClose: ; //do nothing
mrShow: ; //do nothing
mrVersion:
MiscRequest.FileRequest.FileName := PROTOCOL_VERSION+#0;
end;
Stream.Write(MiscRequest, SizeOf(MiscRequest));
Result := SendMessage(Stream);
finally
Stream.Free;
end;
end;
end. end.

View File

@ -1,4 +1,8 @@
{ Copyright (C) <2005> <Andrew Haines> lazchmhelp.pas { Copyright (C) <2005-2013> <Andrew Haines>, Lazarus contributors
lazchmhelp.pas
Lazarus IDE support for lhelp/chm help files. Can start and control lhelp application.
This source is free software; you can redistribute it and/or modify it under This source is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free the terms of the GNU General Public License as published by the Free
@ -41,7 +45,7 @@ type
TChmHelpViewer = class(THelpViewer) TChmHelpViewer = class(THelpViewer)
private private
fHelpExe: String; fHelpExe: String;
fHelpLabel: String; fHelpLabel: String; //ID used for SimpleIPC identification
fHelpConnection: TLHelpConnection; fHelpConnection: TLHelpConnection;
fCHMSearchPath: String; fCHMSearchPath: String;
fHelpExeParams: String; fHelpExeParams: String;
@ -77,6 +81,7 @@ type
function GetHelpFilesPath: String; // macros resolved, see property HelpFilesPath function GetHelpFilesPath: String; // macros resolved, see property HelpFilesPath
published published
property HelpEXE: String read fHelpEXE write SetHelpEXE; // with macros, see GetHelpEXE property HelpEXE: String read fHelpEXE write SetHelpEXE; // with macros, see GetHelpEXE
// ID used for SimpleIPC communication with the help viewer
property HelpLabel: String read GetHelpLabel write SetHelpLabel; property HelpLabel: String read GetHelpLabel write SetHelpLabel;
property HelpFilesPath: String read fCHMSearchPath write SetChmsFilePath; // directories separated with semicolon, with macros, see GetHelpFilesPath property HelpFilesPath: String read fCHMSearchPath write SetChmsFilePath; // directories separated with semicolon, with macros, see GetHelpFilesPath
property HelpExeParams: String read fHelpExeParams write fHelpExeParams; property HelpExeParams: String read fHelpExeParams write fHelpExeParams;
@ -115,8 +120,12 @@ end;
function TChmHelpViewer.GetHelpLabel: String; function TChmHelpViewer.GetHelpLabel: String;
begin begin
// fHelpLable is used for SimpleIPC server id;
// lhelp protocol specifies server-dependent constant string
// followed by string representation of last 5 digits of the processID
// padded with 00000 at the right
if Length(fHelpLabel) = 0 then if Length(fHelpLabel) = 0 then
fHelpLabel := 'lazhelp'; fHelpLabel := 'lazhelp'+copy(inttostr(GetProcessID)+'00000',1,5);
Result := fHelpLabel; Result := fHelpLabel;
end; end;
@ -418,6 +427,7 @@ end;
procedure TChmHelpViewer.ShowAllHelp(Sender: TObject); procedure TChmHelpViewer.ShowAllHelp(Sender: TObject);
var var
Response: TLHelpResponse;
SearchPath: String; //; delimited list of directories SearchPath: String; //; delimited list of directories
HelpExeFileName: String; HelpExeFileName: String;
begin begin
@ -434,13 +444,27 @@ begin
end; end;
SearchPath := GetHelpFilesPath; SearchPath := GetHelpFilesPath;
// Start up server if needed // Start up help viewer if needed - and tell it to hide
if not(fHelpConnection.ServerRunning) then if not(fHelpConnection.ServerRunning) then
begin begin
fHelpConnection.StartHelpServer(HelpLabel, HelpExeFileName); fHelpConnection.StartHelpServer(HelpLabel, HelpExeFileName, true);
Response:=fHelpConnection.RunMiscCommand(mrVersion);
if Response<>srSuccess then
begin
debugln('Help viewer does not support our protocol version.');
end
else
begin
// Open all chm files after it has started, while still hidden
OpenAllCHMsInSearchPath(SearchPath);
// Instruct viewer to show its GUI
Response:=fHelpConnection.RunMiscCommand(mrShow);
if Response<>srSuccess then
begin
debugln('Help viewer failed to respond to mrShow command.');
end;
end;
end; end;
// Open all chm files after it has started
OpenAllCHMsInSearchPath(SearchPath);
end; end;
function TChmHelpViewer.ShowNode(Node: THelpNode; var ErrMsg: string function TChmHelpViewer.ShowNode(Node: THelpNode; var ErrMsg: string
@ -569,7 +593,7 @@ procedure TChmHelpViewer.Load(Storage: TConfigStorage);
begin begin
HelpEXE:=Storage.GetValue('CHMHelp/Exe',''); HelpEXE:=Storage.GetValue('CHMHelp/Exe','');
HelpExeParams := Storage.GetValue('CHMHelp/ExeParams',''); HelpExeParams := Storage.GetValue('CHMHelp/ExeParams','');
HelpLabel:=Storage.GetValue('CHMHelp/Name','lazhelp'); HelpLabel:=Storage.GetValue('CHMHelp/Name','lazhelp')+inttostr(GetProcessID);
HelpFilesPath := Storage.GetValue('CHMHelp/FilesPath',''); HelpFilesPath := Storage.GetValue('CHMHelp/FilesPath','');
end; end;