mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-02 04:43:40 +02:00
1079 lines
30 KiB
ObjectPascal
1079 lines
30 KiB
ObjectPascal
{ Copyright (C) 2005-2014 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
|
|
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)
|
|
any later version.
|
|
|
|
This code is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
|
|
details.
|
|
|
|
A copy of the GNU General Public License is available on the World Wide Web
|
|
at <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing
|
|
to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
|
|
Boston, MA 02110-1335, USA.
|
|
}
|
|
{
|
|
Icons from Tango theme:
|
|
http://tango.freedesktop.org/Tango_Icon_Library
|
|
}
|
|
|
|
unit lhelpcore;
|
|
|
|
{$IFDEF LNET_VISUAL}
|
|
{$DEFINE USE_LNET} // you must manually add the lnetvisual.lpk package to the dependancy list
|
|
{$ELSE}
|
|
{$NOTE You can add http capability to lhelp by adding the lnetvisual package v0.6.3 or greater requirement to lhelp.}
|
|
{$ENDIF}
|
|
|
|
{$IFDEF UNIX}
|
|
{$if FPC_FULLVERSION <= 20700}
|
|
{$DEFINE STALE_PIPE_WORKAROUND}
|
|
{$ENDIF}
|
|
{$ENDIF}
|
|
|
|
{$mode objfpc}{$H+}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, SimpleIPC, Laz2_XMLCfg,
|
|
// LCL
|
|
Forms, Controls, Dialogs, Buttons, ComCtrls, ExtCtrls, Menus, StdCtrls,
|
|
LCLProc, LCLType, LCLIntf, DefaultTranslator,
|
|
// LazUtils
|
|
LazFileUtils, LazUTF8, LazLogger,
|
|
// ChmHelp
|
|
{$IFDEF USE_LNET}HTTPContentProvider,{$ENDIF}
|
|
BaseContentProvider, ChmContentProvider, lhelpstrconsts;
|
|
|
|
type
|
|
|
|
{ TContentTab }
|
|
|
|
TContentTab = class(TTabSheet)
|
|
private
|
|
fContentProvider: TBaseContentProvider;
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
property ContentProvider: TBaseContentProvider read fContentProvider write fContentProvider;
|
|
end;
|
|
|
|
{ THelpForm }
|
|
|
|
THelpForm = class(TForm)
|
|
ApplicationProperties1: TApplicationProperties;
|
|
FileMenuCloseItem: TMenuItem;
|
|
FileMenuExitItem: TMenuItem;
|
|
FileMenuItem: TMenuItem;
|
|
FileMenuOpenItem: TMenuItem;
|
|
FileSeperater: TMenuItem;
|
|
ImageList1: TImageList;
|
|
ImageListToolbar: TImageList;
|
|
MainMenu1: TMainMenu;
|
|
FileMenuOpenURLItem: TMenuItem;
|
|
HelpMenuItem: TMenuItem;
|
|
AboutItem: TMenuItem;
|
|
FileMenuOpenRecentItem: TMenuItem;
|
|
ViewShowStatus: TMenuItem;
|
|
ViewShowSepTabs: TMenuItem;
|
|
PageControl: TPageControl;
|
|
OpenDialog1: TOpenDialog;
|
|
ToolBar1: TToolBar;
|
|
HomeBttn: TToolButton;
|
|
BackBttn: TToolButton;
|
|
ForwardBttn: TToolButton;
|
|
FileButton: TToolButton;
|
|
ToolButton1: TToolButton;
|
|
ViewMenuContents: TMenuItem;
|
|
ViewMenuItem: TMenuItem;
|
|
procedure AboutItemClick(Sender: TObject);
|
|
procedure BackToolBtnClick(Sender: TObject);
|
|
procedure FileMenuCloseItemClick(Sender: TObject);
|
|
procedure FileMenuExitItemClick(Sender: TObject);
|
|
procedure FileMenuOpenItemClick(Sender: TObject);
|
|
procedure FileMenuOpenURLItemClick(Sender: TObject);
|
|
procedure FormClose(Sender: TObject; var {%H-}CloseAction: TCloseAction);
|
|
procedure FormCreate(Sender: TObject);
|
|
procedure FormKeyUp(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
|
|
procedure FormShow(Sender: TObject);
|
|
procedure ForwardToolBtnClick(Sender: TObject);
|
|
procedure HomeToolBtnClick(Sender: TObject);
|
|
procedure PageControlChange(Sender: TObject);
|
|
procedure PageControlEnter(Sender: TObject);
|
|
procedure ViewMenuContentsClick(Sender: TObject);
|
|
procedure ViewMenuItemClick(Sender: TObject);
|
|
procedure ViewShowSepTabsClick(Sender: TObject);
|
|
procedure ViewShowStatusClick(Sender: TObject);
|
|
private
|
|
{ private declarations }
|
|
// SimpleIPC server name (including unique part as per help protocol)
|
|
fServerName: String;
|
|
// Receives commands from IDE
|
|
fInputIPC: TSimpleIPCServer;
|
|
// Sends responses back to IDE
|
|
// only used if lhelp started with --ipcname to indicate
|
|
// IPC communication method should be used
|
|
fOutputIPC: TSimpleIPCClient;
|
|
fInputIPCTimer: TTimer;
|
|
fContext: LongInt; // used once when we are started on the command line with --context
|
|
fConfig: TXMLConfig;
|
|
fShowSepTabs: Boolean;
|
|
fShowStatus: Boolean;
|
|
fHasShowed: Boolean;
|
|
fHide: boolean; //If yes, start with content hidden. Otherwise start normally
|
|
fUpdateCount: Integer;
|
|
// Keep track of whether size/position preferences were loaded and applied to form
|
|
fLayoutApplied: boolean;
|
|
// Applies layout (size/position/fullscreen) preferences once in lhelp lifetime
|
|
// Needs LoadPreference to be run first to get fConfig object.
|
|
procedure ApplyLayoutPreferencesOnce;
|
|
// Load preferences. Preferences are unique for server-lhelp pairs and plain lhelp
|
|
procedure LoadPreferences(AIPCName: String);
|
|
// Saves preferences. Uses existing config loaded by LoadPreferences
|
|
procedure SavePreferences;
|
|
// Add filename to recent files (MRU) list
|
|
procedure AddRecentFile(AFileName: String);
|
|
procedure ContentTitleChange({%H-}sender: TObject);
|
|
procedure OpenRecentItemClick(Sender: TObject);
|
|
// Send response back to server (IDE)
|
|
// Used to acknowledge commands from the server
|
|
procedure SendResponse(Response: DWord);
|
|
// Wait for message from server (IDE) and process
|
|
procedure ServerMessage(Sender: TObject);
|
|
// Parse any given command line options
|
|
procedure ReadCommandLineOptions;
|
|
// Start simple IPC server/client
|
|
// ServerName can be the variable passed to --ipcname
|
|
// 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);
|
|
function ActivePage: TContentTab;
|
|
// Update UI visibility
|
|
procedure RefreshState;
|
|
procedure ShowError(AError: String);
|
|
// Set keyup handler for control (and any child controls)
|
|
procedure SetKeyUp(AControl: TControl);
|
|
// BeginUpdate tells each content provider to possibly stop some events
|
|
procedure BeginUpdate;
|
|
// EndUpdate tells each content provider to resume normal behavior
|
|
procedure EndUpdate;
|
|
public
|
|
{ public declarations }
|
|
end;
|
|
|
|
|
|
var
|
|
HelpForm: THelpForm;
|
|
// Sends messages to the IDE
|
|
IPCClient: TSimpleIPCClient;
|
|
// Receives messages from the IDE
|
|
IPCServer: TSimpleIPCServer;
|
|
|
|
const
|
|
INVALID_FILE_TYPE = 1;
|
|
VERSION_STAMP = '2014-10-16'; //used in displaying version in about form etc
|
|
|
|
implementation
|
|
|
|
{$R *.lfm}
|
|
|
|
uses
|
|
LHelpControl;
|
|
|
|
const
|
|
DigitsInPID=5; // Number of digits in the formatted PID according to the Help Protocol
|
|
|
|
type
|
|
TRecentMenuItem = class(TMenuItem)
|
|
public
|
|
URL: String;
|
|
end;
|
|
|
|
{ THelpForm }
|
|
|
|
|
|
procedure THelpForm.BackToolBtnClick(Sender: TObject);
|
|
begin
|
|
if Assigned(ActivePage) then ActivePage.ContentProvider.GoBack;
|
|
end;
|
|
|
|
procedure THelpForm.AboutItemClick(Sender: TObject);
|
|
var
|
|
f: TForm;
|
|
l: TLabel;
|
|
b: TButton;
|
|
begin
|
|
f := TForm.Create(Application);
|
|
try
|
|
f.Caption := slhelp_About;
|
|
f.BorderStyle := bsDialog;
|
|
f.Position := poMainFormCenter;
|
|
f.Constraints.MinWidth := 150;
|
|
f.Constraints.MaxWidth := 350;
|
|
l := TLabel.Create(f);
|
|
l.Parent := f;;
|
|
l.Align := alTop;
|
|
l.BorderSpacing.Around := 6;
|
|
l.Caption := Format(slhelp_LHelpCHMFileViewerVersionCopyrightCAndrewHainesLaz, [LineEnding, VERSION_STAMP, LineEnding +
|
|
LineEnding, LineEnding]);
|
|
l.AutoSize := True;
|
|
//l.WordWrap := True; {don't wrap author's name}
|
|
b := TButton.Create(f);
|
|
b.Parent := f;
|
|
b.BorderSpacing.Around := 6;
|
|
b.Anchors := [akTop, akLeft];
|
|
b.AnchorSide[akTop].Control := l;
|
|
b.AnchorSide[akTop].Side := asrBottom;
|
|
b.AnchorSide[akLeft].Control := f;
|
|
b.AnchorSide[akLeft].Side := asrCenter;
|
|
b.Caption := slhelp_Ok;
|
|
b.ModalResult := mrOk;
|
|
f.AutoSize := False;
|
|
f.AutoSize := True;
|
|
f.ShowModal;
|
|
finally
|
|
f.free;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure THelpForm.FileMenuCloseItemClick(Sender: TObject);
|
|
begin
|
|
if Assigned(ActivePage) then
|
|
begin
|
|
ViewMenuContentsClick(Self);
|
|
ActivePage.Free;
|
|
RefreshState;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.FileMenuExitItemClick(Sender: TObject);
|
|
begin
|
|
Close;
|
|
end;
|
|
|
|
procedure THelpForm.FileMenuOpenItemClick(Sender: TObject);
|
|
var
|
|
TimerWasOn: boolean;
|
|
begin
|
|
// Work around bug 25529: Slow dialog boxes for Windows Vista+ with
|
|
// themes enabled
|
|
// Stop listening to incoming server messages while busy showing dialog
|
|
if assigned(fInputIPCTimer) Then
|
|
begin
|
|
TimerWasOn := fInputIPCTimer.Enabled;
|
|
fInputIPCTimer.Enabled := False;
|
|
end;
|
|
|
|
try
|
|
if OpenDialog1.Execute then
|
|
begin
|
|
if OpenURL('file://'+OpenDialog1.FileName) = Ord(srSuccess) then
|
|
AddRecentFile('file://'+OpenDialog1.FileName)
|
|
else
|
|
MessageDlg(Format(slhelp_NotFound, [OpenDialog1.FileName]), mtError, [mbOK], 0);
|
|
RefreshState;
|
|
end;
|
|
finally
|
|
if assigned(fInputIPCTimer) Then
|
|
begin
|
|
fInputIPCTimer.Enabled := TimerWasOn;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.FileMenuOpenURLItemClick(Sender: TObject);
|
|
var
|
|
fRes: String;
|
|
URLSAllowed: String;
|
|
Protocol: TStrings;
|
|
i: Integer;
|
|
begin
|
|
Protocol := GetContentProviderList;
|
|
try
|
|
URLSAllowed:='';
|
|
for i := 0 to Protocol.Count-1 do
|
|
begin
|
|
if i < 1 then
|
|
URLSAllowed := URLSAllowed + Protocol[i]
|
|
else
|
|
URLSAllowed := URLSAllowed + ', ' +Protocol[i]
|
|
end;
|
|
finally
|
|
Protocol.Free;
|
|
end;
|
|
|
|
URLSAllowed := Trim(URLSAllowed);
|
|
|
|
fRes:='';
|
|
if InputQuery(slhelp_PleaseEnterAURL,
|
|
Format(slhelp_SupportedURLTypeS, [URLSAllowed]), fRes) then
|
|
begin
|
|
if OpenURL(fRes) = ord(srSuccess) then
|
|
AddRecentFile(fRes);
|
|
RefreshState;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.FormClose(Sender: TObject; var CloseAction: TCloseAction);
|
|
begin
|
|
//close all tabs to avoid AV with many tabs
|
|
while Assigned(ActivePage) do
|
|
ActivePage.Free;
|
|
////was before: close tab
|
|
////FileMenuCloseItemClick(Sender);
|
|
|
|
Visible := false;
|
|
Application.ProcessMessages;
|
|
StopComms;
|
|
SavePreferences;
|
|
end;
|
|
|
|
procedure THelpForm.FormCreate(Sender: TObject);
|
|
begin
|
|
FileMenuItem.Caption := slhelp_File;
|
|
FileMenuOpenItem.Caption := slhelp_Open;
|
|
FileMenuOpenRecentItem.Caption := slhelp_OpenRecent;
|
|
FileMenuOpenURLItem.Caption := slhelp_OpenURL;
|
|
FileMenuCloseItem.Caption := slhelp_Close;
|
|
FileMenuExitItem.Caption := slhelp_EXit;
|
|
ViewMenuItem.Caption := slhelp_View;
|
|
ViewMenuContents.Caption := slhelp_ShowContents;
|
|
ViewShowStatus.Caption := slhelp_OpenNewTabWithStatusBar;
|
|
ViewShowSepTabs.Caption := slhelp_OpenNewFileInSeparateTab;
|
|
HelpMenuItem.Caption := slhelp_Help;
|
|
AboutItem.Caption := slhelp_About2;
|
|
|
|
OpenDialog1.Title := slhelp_OpenExistingFile;
|
|
OpenDialog1.Filter := slhelp_HelpFilesChmChmAllFiles;
|
|
|
|
fContext := -1;
|
|
// Safe default:
|
|
fHide := false;
|
|
// ReadCommandLineOptions will set fHide if requested
|
|
ReadCommandLineOptions;
|
|
LoadPreferences(fServerName);
|
|
// Only start IPC if server name passed in --ipcname
|
|
if fServerName <> '' then
|
|
begin
|
|
StartComms(fServerName);
|
|
end;
|
|
// If user wants lhelp to hide, hide entire form.
|
|
if fHide then
|
|
WindowState := wsMinimized
|
|
else
|
|
RefreshState;
|
|
SetKeyUp(Self);
|
|
end;
|
|
|
|
procedure THelpForm.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
|
|
begin
|
|
if Key = VK_ESCAPE then
|
|
Close;
|
|
// Backspace: go to previous page (as if BackBttn were clicked)
|
|
if Key = VK_BACK then
|
|
if Assigned(ActivePage) then ActivePage.ContentProvider.GoBack;
|
|
end;
|
|
|
|
procedure THelpForm.FormShow(Sender: TObject);
|
|
begin
|
|
if FHasShowed then
|
|
Exit;
|
|
FHasShowed := True;
|
|
end;
|
|
|
|
procedure THelpForm.ForwardToolBtnClick(Sender: TObject);
|
|
begin
|
|
if Assigned(ActivePage) then ActivePage.ContentProvider.GoForward;
|
|
end;
|
|
|
|
procedure THelpForm.HomeToolBtnClick(Sender: TObject);
|
|
begin
|
|
if Assigned(ActivePage) then ActivePage.ContentProvider.GoHome;
|
|
end;
|
|
|
|
procedure THelpForm.PageControlChange(Sender: TObject);
|
|
begin
|
|
RefreshState;
|
|
end;
|
|
|
|
procedure THelpForm.PageControlEnter(Sender: TObject);
|
|
begin
|
|
RefreshState;
|
|
end;
|
|
|
|
procedure THelpForm.ApplyLayoutPreferencesOnce;
|
|
begin
|
|
if not Assigned(fConfig) then exit;
|
|
if (not fHide) and (not fLayoutApplied) then
|
|
begin
|
|
if fConfig.GetValue('Position/Maximized', false) then
|
|
begin
|
|
Windowstate := wsMaximized
|
|
end
|
|
else
|
|
begin
|
|
Left := fConfig.GetValue('Position/Left/Value', Left);
|
|
Top := fConfig.GetValue('Position/Top/Value', Top);
|
|
Width := fConfig.GetValue('Position/Width/Value', Width);
|
|
Height := fConfig.GetValue('Position/Height/Value', Height);
|
|
end;
|
|
// Keep track so we do not reapply initial settings as user may have
|
|
// changed size etc in the meantime.
|
|
fLayoutApplied := true;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.ViewMenuContentsClick(Sender: TObject);
|
|
begin
|
|
// TabsControl property in TChmContentProvider
|
|
if Assigned(ActivePage) then
|
|
begin
|
|
with TChmContentProvider(ActivePage.ContentProvider) do
|
|
begin
|
|
TabsControl.Visible := not TabsControl.Visible;
|
|
Splitter.Visible := TabsControl.Visible;
|
|
Splitter.Left := TabsControl.Left + 4; //for splitter to move righter
|
|
ViewMenuContents.Checked := TabsControl.Visible;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.ViewMenuItemClick(Sender: TObject);
|
|
begin
|
|
ViewMenuContents.Checked :=
|
|
Assigned(ActivePage) and
|
|
(ActivePage.ContentProvider is TChmContentProvider) and
|
|
(ActivePage.ContentProvider as TChmContentProvider).TabsControl.Visible;
|
|
ViewShowSepTabs.Checked := fShowSepTabs;
|
|
ViewShowStatus.Checked := fShowStatus;
|
|
end;
|
|
|
|
procedure THelpForm.ViewShowSepTabsClick(Sender: TObject);
|
|
begin
|
|
fShowSepTabs := not fShowSepTabs;
|
|
end;
|
|
|
|
procedure THelpForm.ViewShowStatusClick(Sender: TObject);
|
|
begin
|
|
fShowStatus := not fShowStatus;
|
|
end;
|
|
|
|
procedure THelpForm.LoadPreferences(AIPCName: String);
|
|
var
|
|
PrefFile: String;
|
|
RecentCount: Integer;
|
|
ServerPart: String;
|
|
i: Integer;
|
|
begin
|
|
PrefFile := GetAppConfigDirUTF8(False);
|
|
ForceDirectoriesUTF8(PrefFile);
|
|
// --ipcname passes a server ID that consists of a
|
|
// server-dependent constant together with a process ID.
|
|
// Strip out the formatted process ID to get fixed config file names for
|
|
// one server
|
|
ServerPart := Copy(AIPCName, 1, length(AIPCName)-DigitsInPID);
|
|
PrefFile := Format('%slhelp-%s.conf',[IncludeTrailingPathDelimiter(PrefFile), ServerPart]);
|
|
|
|
fConfig := TXMLConfig.Create(Self);
|
|
fConfig.Filename := PrefFile;
|
|
|
|
// Restore window but only if currently not being asked to hide
|
|
ApplyLayoutPreferencesOnce;
|
|
OpenDialog1.FileName := fConfig.GetValue('LastFileOpen/Value', OpenDialog1.FileName);
|
|
|
|
RecentCount:= fConfig.GetValue('Recent/ItemCount/Value', 0);
|
|
// downto since oldest are knocked off the list:
|
|
for i := RecentCount-1 downto 0 do
|
|
AddRecentFile(fConfig.GetValue('Recent/Item'+IntToStr(i)+'/Value',''));
|
|
|
|
fShowSepTabs := fConfig.GetValue('OpenSepTabs/Value', true);
|
|
fShowStatus := fConfig.GetValue('OpenWithStatus/Value', true);
|
|
end;
|
|
|
|
procedure THelpForm.SavePreferences;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if not Assigned(fConfig) then
|
|
exit; //silently abort
|
|
|
|
if (WindowState <> wsMaximized) then
|
|
begin
|
|
fConfig.SetValue('Position/Maximized', false);
|
|
fConfig.SetValue('Position/Left/Value', Left);
|
|
fConfig.SetValue('Position/Top/Value', Top);
|
|
fConfig.SetValue('Position/Width/Value', Width);
|
|
fConfig.SetValue('Position/Height/Value', Height);
|
|
end
|
|
else
|
|
begin
|
|
fConfig.SetValue('Position/Maximized', true);
|
|
end;
|
|
|
|
fConfig.SetValue('LastFileOpen/Value', OpenDialog1.FileName);
|
|
|
|
fConfig.SetValue('Recent/ItemCount/Value', FileMenuOpenRecentItem.Count);
|
|
// downto since oldest are knocked off the list:
|
|
for i := 0 to FileMenuOpenRecentItem.Count-1 do
|
|
fConfig.SetValue('Recent/Item'+IntToStr(i)+'/Value', TRecentMenuItem(FileMenuOpenRecentItem.Items[I]).URL);
|
|
|
|
fConfig.SetValue('OpenSepTabs/Value', fShowSepTabs);
|
|
fConfig.SetValue('OpenWithStatus/Value', fShowStatus);
|
|
|
|
fConfig.Flush;
|
|
fConfig.Free;
|
|
end;
|
|
|
|
procedure THelpForm.AddRecentFile(AFileName: String);
|
|
var
|
|
Item : TRecentMenuItem;
|
|
MaxHistory: longint;
|
|
i: Integer;
|
|
begin
|
|
for i := FileMenuOpenRecentItem.Count-1 downto 0 do
|
|
if TRecentMenuItem(FileMenuOpenRecentItem.Items[i]).URL = AFileName then
|
|
begin
|
|
FileMenuOpenRecentItem.Delete(i);
|
|
end;
|
|
Item := TRecentMenuItem.Create(FileMenuOpenRecentItem);
|
|
Item.Caption:=ExtractFileNameOnly(AFileName);
|
|
Item.URL:=AFileName;
|
|
Item.OnClick:=@OpenRecentItemClick;
|
|
Item.Hint:=Item.URL;
|
|
FileMenuOpenRecentItem.Insert(0, Item);
|
|
|
|
MaxHistory := fConfig.GetValue('Recent/HistoryCount/Value', 10);
|
|
|
|
if FileMenuOpenRecentItem.Count > 0 then
|
|
FileMenuOpenRecentItem.Enabled:=True;
|
|
|
|
if FileMenuOpenRecentItem.Count > MaxHistory then
|
|
FileMenuOpenRecentItem.Items[MaxHistory-1].Free;
|
|
end;
|
|
|
|
procedure THelpForm.ContentTitleChange(sender: TObject);
|
|
begin
|
|
if ActivePage = nil then
|
|
Exit;
|
|
Caption := Format(slhelp_LHelp2, [ActivePage.fContentProvider.Title]);
|
|
end;
|
|
|
|
procedure THelpForm.OpenRecentItemClick(Sender: TObject);
|
|
var
|
|
Item: TRecentMenuItem absolute Sender;
|
|
res: DWord;
|
|
begin
|
|
res := OpenURL(Item.URL);
|
|
if res = Ord(srSuccess) then
|
|
AddRecentFile(Item.URL)
|
|
else
|
|
MessageDlg(Format(slhelp_NotFound, [Item.URL]), mtError, [mbOK], 0);
|
|
end;
|
|
|
|
procedure THelpForm.SendResponse(Response: DWord);
|
|
var
|
|
Stream: TMemoryStream;
|
|
begin
|
|
Stream := TMemoryStream.Create;
|
|
try
|
|
Stream.WriteDWord(Response);
|
|
if assigned(fOutputIPC) and fOutputIPC.Active then
|
|
fOutputIPC.SendMessage(mtUnknown, Stream);
|
|
finally
|
|
Stream.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.ServerMessage(Sender: TObject);
|
|
var
|
|
UrlReq: TUrlRequest;
|
|
FileReq: TFileRequest;
|
|
ConReq: TContextRequest;
|
|
MiscReq: TMiscRequest;
|
|
MustClose: boolean=false;
|
|
Stream: TStream;
|
|
Res: LongWord;
|
|
Url: String='';
|
|
begin
|
|
while fInputIPC.PeekMessage(5, True) do
|
|
begin
|
|
Stream := fInputIPC.MsgData;
|
|
Stream.Position := 0;
|
|
FillByte(FileReq{%H-},SizeOf(FileReq),0);
|
|
Stream.Read(FileReq, SizeOf(FileReq));
|
|
Res := Ord(srError); //fail by default
|
|
case FileReq.RequestType of
|
|
rtFile:
|
|
begin
|
|
Url := 'file://'+FileReq.FileName;
|
|
Res := OpenURL(URL);
|
|
//debugln('got rtfile, filename '+filereq.filename);
|
|
end;
|
|
rtUrl:
|
|
begin
|
|
Stream.Position := 0;
|
|
FillByte(UrlReq{%H-},SizeOf(UrlReq),0);
|
|
Stream.Read(UrlReq, SizeOf(UrlReq));
|
|
if UrlReq.FileRequest.FileName <> '' then
|
|
begin
|
|
Url := 'file://'+UrlReq.FileRequest.FileName;
|
|
Res := OpenUrl(URL+'://'+UrlReq.Url)
|
|
end
|
|
else
|
|
begin
|
|
Url := UrlReq.Url;
|
|
Res := OpenURL(Url);
|
|
end;
|
|
//debugln('got rturl, filename '+urlreq.filerequest.filename+', url '+urlreq.url);
|
|
end;
|
|
rtContext:
|
|
begin
|
|
Stream.Position := 0;
|
|
FillByte(ConReq{%H-},SizeOf(ConReq),0);
|
|
Stream.Read(ConReq, SizeOf(ConReq));
|
|
Url := 'file://'+FileReq.FileName;
|
|
Res := OpenURL(Url, ConReq.HelpContext);
|
|
//debugln('got rtcontext, filename '+filereq.filename+', context '+inttostr(ConReq.HelpContext));
|
|
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);
|
|
//debugln('got rtmisc/mrClose');
|
|
end;
|
|
mrShow:
|
|
begin
|
|
fHide := false;
|
|
if WindowState = wsMinimized then
|
|
WindowState := wsNormal;
|
|
RefreshState;
|
|
Res := ord(srSuccess);
|
|
//debugln('got rtmisc/mrShow');
|
|
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
|
|
//debugln('got rtmisc/');
|
|
end;
|
|
mrBeginUpdate:
|
|
begin
|
|
BeginUpdate;
|
|
Res := ord(srSuccess);
|
|
end;
|
|
mrEndUpdate:
|
|
begin
|
|
EndUpdate;
|
|
Res := ord(srSuccess);
|
|
end
|
|
else {Unknown request}
|
|
Res := ord(srUnknown);
|
|
end;
|
|
end; //rtMisc
|
|
end;
|
|
|
|
// This may take some time which may allow receiving end to get ready for
|
|
// receiving messages
|
|
if (URL<>'') and (Res = Ord(srSuccess)) then
|
|
AddRecentFile(Url);
|
|
// Receiving end may not yet be ready (observed with an Intel Core i7),
|
|
// so perhaps wait a bit?
|
|
// Unfortunately, the delay time is guesswork=>Sleep(80)?
|
|
SendResponse(Res); //send response again in case first wasn't picked up
|
|
// Keep after SendResponse to avoid timing issues (e.g. writing to log file):
|
|
//debugln('Just sent TLHelpResponse code: '+inttostr(Res));
|
|
|
|
if MustClose then
|
|
begin
|
|
Application.ProcessMessages;
|
|
Sleep(10);
|
|
Application.Terminate;
|
|
end;
|
|
|
|
// We received mrShow:
|
|
if (MustClose=false) and (fHide=false) then
|
|
begin
|
|
Self.SendToBack;
|
|
Self.BringToFront;
|
|
Self.ShowOnTop;
|
|
// If lhelp was run with hidden parameter, we need to apply
|
|
// layout preferences once:
|
|
ApplyLayoutPreferencesOnce;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.ReadCommandLineOptions;
|
|
var
|
|
X: Integer;
|
|
IsHandled: array[0..50] of boolean;
|
|
URL: String;
|
|
StrItem: PStringItem;
|
|
Filename: String;
|
|
begin
|
|
FillChar(IsHandled{%H-}, 51, 0);
|
|
X := 1;
|
|
while X <= ParamCount do
|
|
begin
|
|
if LowerCase(ParamStrUTF8(X)) = '--ipcname' then
|
|
begin
|
|
// IPC name; includes unique PID or other identifier
|
|
IsHandled[X] := True;
|
|
inc(X);
|
|
if X <= ParamCount then
|
|
begin
|
|
fServerName := ParamStrUTF8(X);
|
|
IsHandled[X] := True;
|
|
inc(X);
|
|
end;
|
|
end
|
|
else if LowerCase(ParamStrUTF8(X)) = '--context' then
|
|
begin
|
|
IsHandled[X] := True;
|
|
inc(X);
|
|
if (X <= ParamCount) then
|
|
if TryStrToInt(ParamStrUTF8(X), fContext) then
|
|
begin
|
|
IsHandled[X] := True;
|
|
inc(X);
|
|
end;
|
|
end
|
|
else if LowerCase(ParamStrUTF8(X)) = '--hide' then
|
|
begin
|
|
IsHandled[X] := True;
|
|
inc(X);
|
|
fHide:=true;
|
|
end
|
|
else
|
|
begin
|
|
IsHandled[X] := copy(ParamStrUTF8(X),1,1)='-'; // ignore other parameters
|
|
inc(X);
|
|
end;
|
|
end;
|
|
|
|
// Loop through a second time for the URL
|
|
for X := 1 to ParamCount do
|
|
if not IsHandled[X] then
|
|
begin
|
|
//DoOpenChm(ParamStrUTF8(X));
|
|
URL := ParamStrUTF8(X);
|
|
if Pos('://', URL) = 0 then
|
|
URL := 'file://'+URL;
|
|
Filename:=URL;
|
|
// if copy(Filename,1,length('file://'))='file://' then
|
|
if pos('file://', FileName) = 1 then
|
|
begin
|
|
System.Delete(Filename,1,length('file://'));
|
|
Filename := SetDirSeparators(Filename);
|
|
if not FileExistsUTF8(Filename) then
|
|
begin
|
|
debugln(['THelpForm.ReadCommandLineOptions file not found "',Filename,'"']);
|
|
continue;
|
|
end;
|
|
end;
|
|
StrItem := New(PStringItem);
|
|
StrItem^.FString := URL;
|
|
Application.QueueAsyncCall(TDataEvent(@LateOpenURL), {%H-}PtrUInt(StrItem));
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
procedure THelpForm.StartComms(ServerName: String);
|
|
// 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.ServerID := ServerName;
|
|
fInputIPC.Global := True;
|
|
fInputIPC.Active := True;
|
|
IPCServer := fInputIPC;
|
|
|
|
// Use timer to check for incoming messages from the IDE
|
|
fInputIPCTimer := TTimer.Create(nil);
|
|
fInputIPCTimer.OnTimer := @ServerMessage;
|
|
fInputIPCTimer.Interval := 200; //milliseconds
|
|
fInputIPCTimer.Enabled := True;
|
|
ServerMessage(nil);
|
|
|
|
fOutputIPC := TSimpleIPCClient.Create(nil);
|
|
fOutputIPC.ServerID := ServerName+'client';
|
|
try
|
|
if fOutputIPC.ServerRunning then
|
|
fOutputIPC.Active := True;
|
|
except
|
|
fOutputIPC.Active := False;
|
|
end;
|
|
IPCClient := fOutputIPC;
|
|
end;
|
|
|
|
procedure THelpForm.StopComms;
|
|
begin
|
|
if fInputIPC <> nil then
|
|
begin
|
|
if fInputIPC.Active then
|
|
fInputIPC.Active := False;
|
|
|
|
FreeAndNil(fInputIPC);
|
|
IPCServer := nil;
|
|
FreeAndNil(fInputIPCTimer);
|
|
end;
|
|
|
|
if fOutputIPC <> nil then
|
|
begin
|
|
if fOutputIPC.Active then
|
|
fOutputIPC.Active := False;
|
|
|
|
FreeAndNil(fOutputIPC);
|
|
IPCClient := nil;
|
|
end;
|
|
end;
|
|
|
|
function THelpForm.OpenURL(const AURL: String; AContext: THelpContext): DWord;
|
|
function GetURLPrefix: String;
|
|
var
|
|
fPos: Integer;
|
|
begin
|
|
fPos := Pos('://', AURL);
|
|
Result := Copy(AURL, 1, fPos+2);
|
|
end;
|
|
var
|
|
fURLPrefix: String;
|
|
fContentProvider: TBaseContentProviderClass;
|
|
fRealContentProvider: TBaseContentProviderClass;
|
|
fPage: TContentTab = nil;
|
|
I: Integer;
|
|
fIsNewPage: Boolean = false;
|
|
begin
|
|
Result := Ord(srInvalidURL);
|
|
fURLPrefix := GetURLPrefix;
|
|
fContentProvider := GetContentProvider(fURLPrefix);
|
|
|
|
if fContentProvider = nil then
|
|
begin
|
|
ShowError(Format(slhelp_CannotHandleThisTypeOfContentForUrl, [fURLPrefix, LineEnding, AURL]));
|
|
Result := Ord(srInvalidURL);
|
|
Exit;
|
|
end;
|
|
fRealContentProvider := fContentProvider.GetProperContentProvider(AURL);
|
|
|
|
if fRealContentProvider = nil then
|
|
begin
|
|
ShowError(Format(slhelp_CannotHandleThisTypeOfSubcontentForUrl, [fURLPrefix, LineEnding, AURL]));
|
|
Result := Ord(srInvalidURL);
|
|
Exit;
|
|
end;
|
|
|
|
if not fShowSepTabs then
|
|
for I := 0 to PageControl.PageCount-1 do
|
|
begin
|
|
if fRealContentProvider.ClassName = TContentTab(PageControl.Pages[I]).ContentProvider.ClassName then
|
|
begin
|
|
fPage := TContentTab(PageControl.Pages[I]);
|
|
if TContentTab(PageControl.Pages[I]).ContentProvider.LoadURL(AURL, AContext) then
|
|
begin
|
|
PageControl.ActivePage := PageControl.Pages[I];
|
|
Result := Ord(srSuccess);
|
|
end
|
|
else
|
|
Result := Ord(srInvalidFile);
|
|
Exit;
|
|
end;
|
|
end;
|
|
|
|
if fPage = nil then
|
|
begin
|
|
// no existing page that can handle this content, so create one
|
|
fIsNewPage := true;
|
|
fPage := TContentTab.Create(PageControl);
|
|
fPage.ContentProvider := fRealContentProvider.Create(fPage, ImageList1);
|
|
fPage.ContentProvider.OnTitleChange := @ContentTitleChange;
|
|
fPage.Parent := PageControl;
|
|
SetKeyUp(fPage);
|
|
fPage.ContentProvider.LoadPreferences(fConfig);
|
|
if fPage.ContentProvider is TChmContentProvider then
|
|
(fPage.ContentProvider as TChmContentProvider).ShowStatusbar := fShowStatus;
|
|
end;
|
|
|
|
if fUpdateCount > 0 then
|
|
fPage.ContentProvider.BeginUpdate;
|
|
|
|
if fPage.ContentProvider.LoadURL(AURL, AContext) then
|
|
begin
|
|
PageControl.ActivePage := fPage;
|
|
RefreshState;
|
|
Result := Ord(srSuccess);
|
|
end
|
|
else begin
|
|
Result := Ord(srInvalidURL);
|
|
if fIsNewPage then
|
|
fPage.Free;
|
|
end;
|
|
|
|
if not fHide then
|
|
ShowOnTop;
|
|
end;
|
|
|
|
|
|
procedure THelpForm.LateOpenURL ( Url: PStringItem ) ;
|
|
begin
|
|
if OpenURL(URL^.FString, fContext) = ord(srSuccess) then
|
|
AddRecentFile(URL^.FString);
|
|
// we reset the context because at this point the file has been loaded and the
|
|
// context shown
|
|
fContext := -1;
|
|
|
|
Dispose(Url);
|
|
RefreshState;
|
|
end;
|
|
|
|
function THelpForm.ActivePage: TContentTab;
|
|
begin
|
|
Result := TContentTab(PageControl.ActivePage);
|
|
end;
|
|
|
|
procedure THelpForm.RefreshState;
|
|
var
|
|
en: Boolean;
|
|
begin
|
|
if fHide then
|
|
begin
|
|
en := false;
|
|
// Hide content page
|
|
if Assigned(ActivePage) then
|
|
begin
|
|
with TChmContentProvider(ActivePage.ContentProvider) do
|
|
begin
|
|
ActivePage.Visible := false;
|
|
Visible := false;
|
|
TabsControl.Visible := false;
|
|
Splitter.Visible := false;
|
|
end;
|
|
end;
|
|
end
|
|
else
|
|
begin
|
|
en := Assigned(ActivePage);
|
|
// Show content page
|
|
if en then
|
|
begin
|
|
with TChmContentProvider(ActivePage.ContentProvider) do
|
|
begin
|
|
ActivePage.Visible := true;
|
|
Visible := true;
|
|
TabsControl.Visible := true;
|
|
Splitter.Visible := true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
BackBttn.Enabled := en;
|
|
ForwardBttn.Enabled := en;
|
|
HomeBttn.Enabled := en;
|
|
FileMenuCloseItem.Enabled := en;
|
|
ViewMenuContents.Enabled := en;
|
|
|
|
if en and not (csDestroying in ActivePage.ComponentState) then
|
|
Caption := Format(slhelp_LHelp2, [ActivePage.fContentProvider.Title])
|
|
else
|
|
Caption := slhelp_LHelp;
|
|
end;
|
|
|
|
procedure THelpForm.ShowError(AError: String);
|
|
begin
|
|
ShowMessage(AError);
|
|
end;
|
|
|
|
procedure THelpForm.SetKeyUp(AControl: TControl);
|
|
var
|
|
WCont: TWinControl absolute AControl;
|
|
i: Integer;
|
|
begin
|
|
if (AControl = nil) or not (AControl.InheritsFrom(TWinControl)) then
|
|
Exit;
|
|
for i := 0 to WCont.ControlCount-1 do
|
|
SetKeyUp(WCont.Controls[i]);
|
|
WCont.OnKeyUp:=@FormKeyUp;
|
|
end;
|
|
|
|
procedure THelpForm.BeginUpdate;
|
|
var
|
|
Tab: TContentTab;
|
|
i: Integer;
|
|
begin
|
|
Inc(fUpdateCount);
|
|
if fUpdateCount = 1 then
|
|
begin
|
|
for i := 0 to PageControl.PageCount-1 do
|
|
begin
|
|
Tab := TContentTab(PageControl.Pages[I]);
|
|
Tab.ContentProvider.BeginUpdate;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure THelpForm.EndUpdate;
|
|
var
|
|
Tab: TContentTab;
|
|
i: Integer;
|
|
begin
|
|
Dec(fUpdateCount);
|
|
if fUpdateCount < 0 then
|
|
fUpdateCount:=0;
|
|
|
|
if fUpdateCount = 0 then
|
|
begin
|
|
for i := 0 to PageControl.PageCount-1 do
|
|
begin
|
|
Tab := TContentTab(PageControl.Pages[I]);
|
|
Tab.ContentProvider.EndUpdate;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TContentTab }
|
|
|
|
constructor TContentTab.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
end;
|
|
|
|
destructor TContentTab.Destroy;
|
|
begin
|
|
fContentProvider.Free;
|
|
inherited Destroy;
|
|
end;
|
|
|
|
finalization
|
|
if IPCServer <> nil then
|
|
FreeAndNil(IPCServer);
|
|
if IPCClient <> nil then
|
|
FreeAndNil(IPCClient);
|
|
end.
|
|
|