whelp.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977
  1. {
  2. This file is part of the Free Pascal Integrated Development Environment
  3. Copyright (c) 1998 by Berczi Gabor
  4. Help support & Borland OA .HLP reader objects and routines
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$R-}
  12. unit WHelp;
  13. interface
  14. uses
  15. {$ifdef Windows}
  16. { placed here to avoid TRect to be found in windows unit
  17. for Windows target whereas its found in objects unit for other targets PM }
  18. windows,
  19. {$endif Windows}
  20. Objects,
  21. WUtils;
  22. const
  23. hscLineBreak = #0;
  24. hscLink = #2;
  25. hscLineStart = #3;
  26. hscCode = #5;
  27. hscDirect = #6; { add the next char directly }
  28. hscCenter = #10;
  29. hscRight = #11;
  30. hscNamedMark = #12;
  31. hscTextAttr = #13;
  32. hscTextColor = #14;
  33. hscNormText = #15;
  34. hscInImage = #16;
  35. type
  36. THelpCtx = longint;
  37. TRecord = packed record
  38. SClass : word;
  39. Size : word;
  40. Data : pointer;
  41. end;
  42. PIndexEntry = ^TIndexEntry;
  43. TIndexEntry = packed record
  44. Tag : PString;
  45. HelpCtx : THelpCtx;
  46. FileID : word;
  47. end;
  48. PKeywordDescriptor = ^TKeywordDescriptor;
  49. TKeywordDescriptor = packed record
  50. FileID : word;
  51. Context : THelpCtx;
  52. end;
  53. PKeywordDescriptors = ^TKeywordDescriptors;
  54. TKeywordDescriptors = array[0..MaxBytes div sizeof(TKeywordDescriptor)-1] of TKeywordDescriptor;
  55. PTopic = ^TTopic;
  56. TTopic = object
  57. HelpCtx : THelpCtx;
  58. FileOfs : longint;
  59. TextSize : sw_word;
  60. Text : PByteArray;
  61. LinkCount : sw_word;
  62. Links : PKeywordDescriptors;
  63. LastAccess : longint;
  64. FileID : word;
  65. Param : PString;
  66. StartNamedMark: integer;
  67. NamedMarks : PUnsortedStringCollection;
  68. ExtData : pointer;
  69. ExtDataSize : longint;
  70. function LinkSize: sw_word;
  71. function GetNamedMarkIndex(const MarkName: string): sw_integer;
  72. end;
  73. PTopicCollection = ^TTopicCollection;
  74. TTopicCollection = object(TSortedCollection)
  75. function At(Index: sw_Integer): PTopic;
  76. procedure FreeItem(Item: Pointer); virtual;
  77. function Compare(Key1, Key2: Pointer): Sw_Integer; virtual;
  78. function SearchTopic(AHelpCtx: THelpCtx): PTopic;
  79. end;
  80. PIndexEntryCollection = ^TIndexEntryCollection;
  81. TIndexEntryCollection = object(TSortedCollection)
  82. function At(Index: Sw_Integer): PIndexEntry;
  83. procedure FreeItem(Item: Pointer); virtual;
  84. function Compare(Key1, Key2: Pointer): Sw_Integer; virtual;
  85. end;
  86. PUnsortedIndexEntryCollection = ^TUnsortedIndexEntryCollection;
  87. TUnsortedIndexEntryCollection = object(TCollection)
  88. function At(Index: Sw_Integer): PIndexEntry;
  89. procedure FreeItem(Item: Pointer); virtual;
  90. end;
  91. PHelpFile = ^THelpFile;
  92. THelpFile = object(TObject)
  93. ID : word;
  94. Topics : PTopicCollection;
  95. IndexEntries : PUnsortedIndexEntryCollection;
  96. constructor Init(AID: word);
  97. function LoadTopic(HelpCtx: THelpCtx): PTopic; virtual;
  98. procedure AddTopic(HelpCtx: THelpCtx; Pos: longint; const Param: string;
  99. ExtData: pointer; ExtDataSize: longint);
  100. procedure AddIndexEntry(const Text: string; AHelpCtx: THelpCtx);
  101. destructor Done; virtual;
  102. public
  103. function LoadIndex: boolean; virtual;
  104. function SearchTopic(HelpCtx: THelpCtx): PTopic; virtual;
  105. function ReadTopic(T: PTopic): boolean; virtual;
  106. private
  107. procedure MaintainTopicCache;
  108. end;
  109. PHelpFileCollection = PCollection;
  110. PHelpFacility = ^THelpFacility;
  111. THelpFacility = object(TObject)
  112. HelpFiles: PHelpFileCollection;
  113. IndexTabSize: sw_integer;
  114. constructor Init;
  115. function AddFile(const FileName, Param: string): PHelpFile;
  116. function AddHelpFile(H: PHelpFile): boolean;
  117. function LoadTopic(SourceFileID: word; Context: THelpCtx): PTopic; virtual;
  118. function TopicSearch(Keyword: string; var FileID: word; var Context: THelpCtx): boolean; virtual;
  119. function BuildIndexTopic: PTopic; virtual;
  120. destructor Done; virtual;
  121. private
  122. LastID: word;
  123. function SearchFile(ID: byte): PHelpFile;
  124. function SearchTopicInHelpFile(F: PHelpFile; Context: THelpCtx): PTopic;
  125. function SearchTopicOwner(SourceFileID: word; Context: THelpCtx): PHelpFile;
  126. end;
  127. THelpFileOpenProc = function(const FileName,Param: string;Index : longint): PHelpFile;
  128. PHelpFileType = ^THelpFileType;
  129. THelpFileType = record
  130. OpenProc : THelpFileOpenProc;
  131. end;
  132. const TopicCacheSize : sw_integer = 10;
  133. HelpStreamBufSize : sw_integer = 4096;
  134. HelpFacility : PHelpFacility = nil;
  135. MaxHelpTopicSize : sw_word = {$ifdef FPC}3*65520{$else}65520{$endif};
  136. function NewTopic(FileID: byte; HelpCtx: THelpCtx; Pos: longint; Param: string;
  137. ExtData: pointer; ExtDataSize: longint): PTopic;
  138. procedure DisposeTopic(P: PTopic);
  139. procedure RenderTopic(Lines: PUnsortedStringCollection; T: PTopic);
  140. procedure BuildTopic(Lines: PUnsortedStringCollection; T: PTopic);
  141. procedure AddLinkToTopic(T: PTopic; AFileID: word; ACtx: THelpCtx);
  142. function NewIndexEntry(Tag: string; FileID: word; HelpCtx: THelpCtx): PIndexEntry;
  143. procedure DisposeIndexEntry(P: PIndexEntry);
  144. procedure DisposeRecord(var R: TRecord);
  145. procedure RegisterHelpFileType(AOpenProc: THelpFileOpenProc);
  146. function GetHelpFileTypeCount: integer;
  147. procedure GetHelpFileType(Index: sw_integer; var HT: THelpFileType);
  148. procedure DoneHelpFilesTypes;
  149. {$ifdef ENDIAN_BIG}
  150. Procedure SwapLong(var x : longint);
  151. Procedure SwapWord(var x : word);
  152. {$endif ENDIAN_BIG}
  153. implementation
  154. uses
  155. {$ifdef Unix}
  156. {$ifdef VER1_0}
  157. linux,
  158. {$else}
  159. baseunix,
  160. unix,
  161. {$endif}
  162. {$endif Unix}
  163. {$IFDEF OS2}
  164. DosCalls,
  165. {$ENDIF OS2}
  166. {$ifdef netwlibc}
  167. Libc,
  168. {$endif}
  169. {$ifdef netware_clib}
  170. nwserv,
  171. {$endif}
  172. Strings,
  173. WConsts;
  174. type
  175. PHelpFileTypeCollection = ^THelpFileTypeCollection;
  176. THelpFileTypeCollection = object(TCollection)
  177. function At(Index: sw_Integer): PHelpFileType;
  178. procedure FreeItem(Item: Pointer); virtual;
  179. end;
  180. const
  181. HelpFileTypes : PHelpFileTypeCollection = nil;
  182. {$ifdef ENDIAN_BIG}
  183. Procedure SwapLong(var x : longint);
  184. var
  185. y : word;
  186. z : word;
  187. Begin
  188. y := (x shr 16) and $FFFF;
  189. y := ((y shl 8) and $FFFF) or ((y shr 8) and $ff);
  190. z := x and $FFFF;
  191. z := ((z shl 8) and $FFFF) or ((z shr 8) and $ff);
  192. x := (longint(z) shl 16) or longint(y);
  193. End;
  194. Procedure SwapWord(var x : word);
  195. var
  196. z : byte;
  197. Begin
  198. z := (x shr 8) and $ff;
  199. x := x and $ff;
  200. x := (x shl 8);
  201. x := x or z;
  202. End;
  203. {$endif ENDIAN_BIG}
  204. function NewHelpFileType(AOpenProc: THelpFileOpenProc): PHelpFileType;
  205. var P: PHelpFileType;
  206. begin
  207. New(P);
  208. with P^ do begin OpenProc:=AOpenProc; end;
  209. NewHelpFileType:=P;
  210. end;
  211. procedure DisposeHelpFileType(P: PHelpFileType);
  212. begin
  213. if Assigned(P) then
  214. Dispose(P);
  215. end;
  216. procedure DoneHelpFilesTypes;
  217. begin
  218. if Assigned(HelpFileTypes) then
  219. Dispose(HelpFileTypes, Done);
  220. end;
  221. function THelpFileTypeCollection.At(Index: sw_Integer): PHelpFileType;
  222. begin
  223. At:=inherited At(Index);
  224. end;
  225. procedure THelpFileTypeCollection.FreeItem(Item: Pointer);
  226. begin
  227. if Assigned(Item) then
  228. DisposeHelpFileType(Item);
  229. end;
  230. procedure RegisterHelpFileType(AOpenProc: THelpFileOpenProc);
  231. begin
  232. if not Assigned(HelpFileTypes) then
  233. New(HelpFileTypes, Init(10,10));
  234. HelpFileTypes^.Insert(NewHelpFileType(AOpenProc));
  235. end;
  236. function GetHelpFileTypeCount: integer;
  237. var Count: integer;
  238. begin
  239. if not Assigned(HelpFileTypes) then
  240. Count:=0
  241. else
  242. Count:=HelpFileTypes^.Count;
  243. GetHelpFileTypeCount:=Count;
  244. end;
  245. procedure GetHelpFileType(Index: sw_integer; var HT: THelpFileType);
  246. begin
  247. HT:=HelpFileTypes^.At(Index)^;
  248. end;
  249. Function GetDosTicks:longint; { returns ticks at 18.2 Hz, just like DOS }
  250. {$IFDEF OS2}
  251. const
  252. QSV_MS_COUNT = 14;
  253. var
  254. L: longint;
  255. begin
  256. DosQuerySysInfo (QSV_MS_COUNT, QSV_MS_COUNT, L, 4);
  257. GetDosTicks := L div 55;
  258. end;
  259. {$ENDIF}
  260. {$IFDEF Unix}
  261. var
  262. tv : TimeVal;
  263. tz : TimeZone;
  264. begin
  265. {$ifdef ver1_0}
  266. GetTimeOfDay(tv); {Timezone no longer used?}
  267. GetDosTicks:=((tv.Sec mod 86400) div 60)*1092+((tv.Sec mod 60)*1000000+tv.USec) div 54945;
  268. {$else}
  269. fpGetTimeOfDay(@tv,@tz);
  270. GetDosTicks:=((tv.tv_Sec mod 86400) div 60)*1092+((tv.tv_Sec mod 60)*1000000+tv.tv_USec) div 54945;
  271. {$endif}
  272. end;
  273. {$endif Unix}
  274. {$ifdef Windows}
  275. begin
  276. GetDosTicks:=(Windows.GetTickCount*5484) div 100;
  277. end;
  278. {$endif Windows}
  279. {$ifdef go32v2}
  280. begin
  281. GetDosTicks:=MemL[$40:$6c];
  282. end;
  283. {$endif go32v2}
  284. {$ifdef TP}
  285. begin
  286. GetDosTicks:=MemL[$40:$6c];
  287. end;
  288. {$endif go32v2}
  289. {$ifdef netwlibc}
  290. var
  291. tv : TTimeVal;
  292. tz : TTimeZone;
  293. begin
  294. fpGetTimeOfDay(tv,tz);
  295. GetDosTicks:=((tv.tv_sec mod 86400) div 60)*1092+((tv.tv_Sec mod 60)*1000000+tv.tv_USec) div 549
  296. end;
  297. {$endif}
  298. {$ifdef netware_clib}
  299. begin
  300. GetDosTicks := Nwserv.GetCurrentTicks;
  301. end;
  302. {$endif}
  303. procedure DisposeRecord(var R: TRecord);
  304. begin
  305. with R do
  306. if (Size>0) and (Data<>nil) then FreeMem(Data, Size);
  307. FillChar(R, SizeOf(R), 0);
  308. end;
  309. function NewTopic(FileID: byte; HelpCtx: THelpCtx; Pos: longint; Param: string;
  310. ExtData: pointer; ExtDataSize: longint): PTopic;
  311. var P: PTopic;
  312. begin
  313. New(P); FillChar(P^,SizeOf(P^), 0);
  314. P^.HelpCtx:=HelpCtx; P^.FileOfs:=Pos; P^.FileID:=FileID;
  315. P^.Param:=NewStr(Param);
  316. if Assigned(ExtData) and (ExtDataSize>0) then
  317. begin
  318. P^.ExtDataSize:=ExtDataSize;
  319. GetMem(P^.ExtData,ExtDataSize);
  320. Move(ExtData^,P^.ExtData^,ExtDataSize);
  321. end;
  322. New(P^.NamedMarks, Init(100,100));
  323. NewTopic:=P;
  324. end;
  325. procedure DisposeTopic(P: PTopic);
  326. begin
  327. if P<>nil then
  328. begin
  329. if (P^.TextSize>0) and (P^.Text<>nil) then
  330. FreeMem(P^.Text,P^.TextSize);
  331. P^.Text:=nil;
  332. if {(P^.LinkCount>0) and }(P^.Links<>nil) then
  333. FreeMem(P^.Links,P^.LinkSize);
  334. P^.Links:=nil;
  335. if P^.Param<>nil then DisposeStr(P^.Param); P^.Param:=nil;
  336. if Assigned(P^.ExtData) then
  337. FreeMem(P^.ExtData{$ifndef FPC},P^.ExtDataSize{$endif});
  338. if Assigned(P^.NamedMarks) then Dispose(P^.NamedMarks, Done); P^.NamedMarks:=nil;
  339. Dispose(P);
  340. end;
  341. end;
  342. function CloneTopic(T: PTopic): PTopic;
  343. var NT: PTopic;
  344. procedure CloneMark(P: PString); {$ifndef FPC}far;{$endif}
  345. begin
  346. NT^.NamedMarks^.InsertStr(GetStr(P));
  347. end;
  348. begin
  349. New(NT);
  350. Move(T^,NT^,SizeOf(NT^));
  351. if NT^.Text<>nil then
  352. begin GetMem(NT^.Text,NT^.TextSize); Move(T^.Text^,NT^.Text^,NT^.TextSize); end;
  353. if NT^.Links<>nil then
  354. begin
  355. GetMem(NT^.Links,NT^.LinkSize);
  356. Move(T^.Links^,NT^.Links^,NT^.LinkSize);
  357. end;
  358. if NT^.Param<>nil then
  359. NT^.Param:=NewStr(T^.Param^);
  360. if Assigned(T^.NamedMarks) then
  361. begin
  362. New(NT^.NamedMarks, Init(T^.NamedMarks^.Count,10));
  363. T^.NamedMarks^.ForEach(@CloneMark);
  364. end;
  365. NT^.ExtDataSize:=T^.ExtDataSize;
  366. if Assigned(T^.ExtData) and (T^.ExtDataSize>0) then
  367. begin
  368. GetMem(NT^.ExtData,NT^.ExtDataSize);
  369. Move(T^.ExtData^,NT^.ExtData^,NT^.ExtDataSize);
  370. end;
  371. CloneTopic:=NT;
  372. end;
  373. procedure RenderTopic(Lines: PUnsortedStringCollection; T: PTopic);
  374. var Size,CurPtr,I,MSize: sw_word;
  375. S: string;
  376. begin
  377. CurPtr:=0;
  378. for I:=0 to Lines^.Count-1 do
  379. begin
  380. S:=GetStr(Lines^.At(I));
  381. Size:=length(S)+1;
  382. Inc(CurPtr,Size);
  383. end;
  384. Size:=CurPtr;
  385. T^.TextSize:=Size; GetMem(T^.Text,T^.TextSize);
  386. CurPtr:=0;
  387. for I:=0 to Lines^.Count-1 do
  388. begin
  389. S:=GetStr(Lines^.At(I)); Size:=length(S); MSize:=Size;
  390. if CurPtr+Size>=T^.TextSize then
  391. MSize:=T^.TextSize-CurPtr;
  392. Move(S[1],PByteArray(T^.Text)^[CurPtr],MSize);
  393. if MSize<>Size then
  394. Break;
  395. Inc(CurPtr,Size);
  396. PByteArray(T^.Text)^[CurPtr]:=ord(hscLineBreak);
  397. Inc(CurPtr);
  398. if CurPtr>=T^.TextSize then Break;
  399. end;
  400. end;
  401. procedure BuildTopic(Lines: PUnsortedStringCollection; T: PTopic);
  402. var Size,CurPtr,MSize: sw_word;
  403. I: sw_integer;
  404. S: string;
  405. begin
  406. CurPtr:=0;
  407. for I:=0 to Lines^.Count-1 do
  408. begin
  409. S:=GetStr(Lines^.At(I));
  410. Size:=length(S);
  411. Inc(CurPtr,Size);
  412. end;
  413. Size:=CurPtr;
  414. T^.TextSize:=Size; GetMem(T^.Text,T^.TextSize);
  415. CurPtr:=0;
  416. for I:=0 to Lines^.Count-1 do
  417. begin
  418. S:=GetStr(Lines^.At(I)); Size:=length(S); MSize:=Size;
  419. if Size>0 then
  420. begin
  421. if CurPtr+Size>=T^.TextSize then
  422. MSize:=T^.TextSize-CurPtr;
  423. Move(S[1],PByteArray(T^.Text)^[CurPtr],MSize);
  424. if MSize<>Size then
  425. Break;
  426. Inc(CurPtr,Size);
  427. end;
  428. if CurPtr>=T^.TextSize then Break;
  429. end;
  430. end;
  431. procedure AddLinkToTopic(T: PTopic; AFileID: word; ACtx: THelpCtx);
  432. var NewSize: word;
  433. NewPtr: pointer;
  434. begin
  435. NewSize:=longint(T^.LinkCount+1)*sizeof(T^.Links^[0]);
  436. GetMem(NewPtr,NewSize);
  437. if Assigned(T^.Links) then
  438. begin
  439. Move(T^.Links^,NewPtr^,T^.LinkSize);
  440. FreeMem(T^.Links,T^.LinkSize);
  441. end;
  442. T^.Links:=NewPtr;
  443. with T^.Links^[T^.LinkCount] do
  444. begin
  445. FileID:=AFileID;
  446. Context:=ACtx;
  447. end;
  448. Inc(T^.LinkCount);
  449. end;
  450. function NewIndexEntry(Tag: string; FileID: word; HelpCtx: THelpCtx): PIndexEntry;
  451. var P: PIndexEntry;
  452. begin
  453. New(P); FillChar(P^,SizeOf(P^), 0);
  454. P^.Tag:=NewStr(Tag); P^.FileID:=FileID; P^.HelpCtx:=HelpCtx;
  455. NewIndexEntry:=P;
  456. end;
  457. procedure DisposeIndexEntry(P: PIndexEntry);
  458. begin
  459. if P<>nil then
  460. begin
  461. if P^.Tag<>nil then DisposeStr(P^.Tag);
  462. Dispose(P);
  463. end;
  464. end;
  465. function TTopic.LinkSize: sw_word;
  466. begin
  467. LinkSize:=LinkCount*SizeOf(Links^[0]);
  468. end;
  469. function TTopic.GetNamedMarkIndex(const MarkName: string): sw_integer;
  470. var I,Index: sw_integer;
  471. begin
  472. Index:=-1;
  473. if Assigned(NamedMarks) then
  474. for I:=0 to NamedMarks^.Count-1 do
  475. if CompareText(GetStr(NamedMarks^.At(I)),MarkName)=0 then
  476. begin
  477. Index:=I;
  478. Break;
  479. end;
  480. GetNamedMarkIndex:=Index;
  481. end;
  482. function TTopicCollection.At(Index: sw_Integer): PTopic;
  483. begin
  484. At:=inherited At(Index);
  485. end;
  486. procedure TTopicCollection.FreeItem(Item: Pointer);
  487. begin
  488. if Item<>nil then DisposeTopic(Item);
  489. end;
  490. function TTopicCollection.Compare(Key1, Key2: Pointer): Sw_Integer;
  491. var K1: PTopic absolute Key1;
  492. K2: PTopic absolute Key2;
  493. R: Sw_integer;
  494. begin
  495. if K1^.HelpCtx<K2^.HelpCtx then R:=-1 else
  496. if K1^.HelpCtx>K2^.HelpCtx then R:= 1 else
  497. R:=0;
  498. Compare:=R;
  499. end;
  500. function TTopicCollection.SearchTopic(AHelpCtx: THelpCtx): PTopic;
  501. var T: TTopic;
  502. P: PTopic;
  503. Index: sw_integer;
  504. begin
  505. T.HelpCtx:=AHelpCtx;
  506. if Search(@T,Index) then
  507. P:=At(Index)
  508. else
  509. P:=nil;
  510. SearchTopic:=P;
  511. end;
  512. function TIndexEntryCollection.At(Index: Sw_Integer): PIndexEntry;
  513. begin
  514. At:=inherited At(Index);
  515. end;
  516. procedure TIndexEntryCollection.FreeItem(Item: Pointer);
  517. begin
  518. if Item<>nil then DisposeIndexEntry(Item);
  519. end;
  520. function TUnsortedIndexEntryCollection.At(Index: Sw_Integer): PIndexEntry;
  521. begin
  522. At:=inherited At(Index);
  523. end;
  524. procedure TUnsortedIndexEntryCollection.FreeItem(Item: Pointer);
  525. begin
  526. if Item<>nil then DisposeIndexEntry(Item);
  527. end;
  528. function TIndexEntryCollection.Compare(Key1, Key2: Pointer): Sw_Integer;
  529. var K1: PIndexEntry absolute Key1;
  530. K2: PIndexEntry absolute Key2;
  531. R: Sw_integer;
  532. S1,S2: string;
  533. T1,T2 : PTopic;
  534. begin
  535. S1:=UpcaseStr(K1^.Tag^); S2:=UpcaseStr(K2^.Tag^);
  536. if S1<S2 then
  537. begin
  538. Compare:=-1;
  539. exit;
  540. end;
  541. if S1>S2 then
  542. begin
  543. Compare:=1;
  544. exit;
  545. end;
  546. (* if assigned(HelpFacility) then
  547. begin
  548. { Try to read the title of the topic }
  549. T1:=HelpFacility^.LoadTopic(K1^.FileID,K1^.HelpCtx);
  550. T2:=HelpFacility^.LoadTopic(K2^.FileID,K2^.HelpCtx);
  551. if assigned(T1^.Text) and assigned(T2^.Text) then
  552. r:=strcomp(pchar(T1^.Text),pchar(T2^.Text))
  553. else
  554. r:=0;
  555. if r>0 then
  556. begin
  557. Compare:=1;
  558. exit;
  559. end;
  560. if r<0 then
  561. begin
  562. Compare:=-1;
  563. exit;
  564. end;
  565. end; *)
  566. if K1^.FileID<K2^.FileID then R:=-1
  567. else if K1^.FileID>K2^.FileID then R:= 1
  568. else if K1^.HelpCtx<K2^.HelpCtx then
  569. r:=-1
  570. else if K1^.HelpCtx>K2^.HelpCtx then
  571. r:=1
  572. else
  573. R:=0;
  574. Compare:=R;
  575. end;
  576. constructor THelpFile.Init(AID: word);
  577. begin
  578. inherited Init;
  579. ID:=AID;
  580. New(Topics, Init(2000,1000));
  581. New(IndexEntries, Init(2000,1000));
  582. end;
  583. procedure THelpFile.AddTopic(HelpCtx: THelpCtx; Pos: longint; const Param: string; ExtData: pointer; ExtDataSize: longint);
  584. begin
  585. Topics^.Insert(NewTopic(ID,HelpCtx,Pos,Param,ExtData,ExtDataSize));
  586. end;
  587. procedure THelpFile.AddIndexEntry(const Text: string; AHelpCtx: THelpCtx);
  588. begin
  589. IndexEntries^.Insert(NewIndexEntry(Text,ID,AHelpCtx));
  590. end;
  591. function THelpFile.LoadTopic(HelpCtx: THelpCtx): PTopic;
  592. var T: PTopic;
  593. begin
  594. T:=SearchTopic(HelpCtx);
  595. if (T<>nil) then
  596. if T^.Text=nil then
  597. begin
  598. MaintainTopicCache;
  599. if ReadTopic(T)=false then
  600. T:=nil;
  601. if (T<>nil) and (T^.Text=nil) then T:=nil;
  602. end;
  603. if T<>nil then
  604. begin
  605. T^.LastAccess:=GetDosTicks;
  606. T:=CloneTopic(T);
  607. end;
  608. LoadTopic:=T;
  609. end;
  610. function THelpFile.LoadIndex: boolean;
  611. begin
  612. Abstract;
  613. LoadIndex:=false; { remove warning }
  614. end;
  615. function THelpFile.SearchTopic(HelpCtx: THelpCtx): PTopic;
  616. var T: PTopic;
  617. begin
  618. T:=Topics^.SearchTopic(HelpCtx);
  619. SearchTopic:=T;
  620. end;
  621. function THelpFile.ReadTopic(T: PTopic): boolean;
  622. begin
  623. Abstract;
  624. ReadTopic:=false; { remove warning }
  625. end;
  626. procedure THelpFile.MaintainTopicCache;
  627. var Count: sw_integer;
  628. MinLRU: longint;
  629. procedure CountThem(P: PTopic); {$ifndef FPC}far;{$endif}
  630. begin if (P^.Text<>nil) or (P^.Links<>nil) then Inc(Count); end;
  631. procedure SearchLRU(P: PTopic); {$ifndef FPC}far;{$endif}
  632. begin if P^.LastAccess<MinLRU then begin MinLRU:=P^.LastAccess; end; end;
  633. var P: PTopic;
  634. begin
  635. Count:=0; Topics^.ForEach(@CountThem);
  636. if (Count>=TopicCacheSize) then
  637. begin
  638. MinLRU:=MaxLongint; P:=nil; Topics^.ForEach(@SearchLRU);
  639. if P<>nil then
  640. begin
  641. FreeMem(P^.Text,P^.TextSize); P^.TextSize:=0; P^.Text:=nil;
  642. FreeMem(P^.Links,P^.LinkSize); P^.LinkCount:=0; P^.Links:=nil;
  643. end;
  644. end;
  645. end;
  646. destructor THelpFile.Done;
  647. begin
  648. if Topics<>nil then Dispose(Topics, Done);
  649. if IndexEntries<>nil then Dispose(IndexEntries, Done);
  650. inherited Done;
  651. end;
  652. constructor THelpFacility.Init;
  653. begin
  654. inherited Init;
  655. New(HelpFiles, Init(10,10));
  656. IndexTabSize:=40;
  657. end;
  658. function THelpFacility.AddFile(const FileName, Param: string): PHelpFile;
  659. var H: PHelpFile;
  660. OK: boolean;
  661. I: integer;
  662. HT: THelpFileType;
  663. begin
  664. OK:=false; H:=nil;
  665. for I:=0 to GetHelpFileTypeCount-1 do
  666. begin
  667. GetHelpFileType(I,HT);
  668. H:=HT.OpenProc(FileName,Param,LastID+1);
  669. if Assigned(H) then
  670. Break;
  671. end;
  672. if Assigned(H) then
  673. OK:=AddHelpFile(H);
  674. if (not OK) and Assigned(H) then begin Dispose(H, Done); H:=nil; end;
  675. AddFile:=H;
  676. end;
  677. function THelpFacility.AddHelpFile(H: PHelpFile): boolean;
  678. begin
  679. if H<>nil then
  680. begin
  681. HelpFiles^.Insert(H);
  682. Inc(LastID);
  683. { H^.ID:=LastID; now already set by OpenProc PM }
  684. end;
  685. AddHelpFile:=H<>nil;
  686. end;
  687. function THelpFacility.SearchTopicOwner(SourceFileID: word; Context: THelpCtx): PHelpFile;
  688. var P: PTopic;
  689. HelpFile: PHelpFile;
  690. function Search(F: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
  691. begin
  692. P:=SearchTopicInHelpFile(F,Context); if P<>nil then HelpFile:=F;
  693. Search:=P<>nil;
  694. end;
  695. begin
  696. HelpFile:=nil;
  697. if SourceFileID=0 then P:=nil else
  698. begin
  699. HelpFile:=SearchFile(SourceFileID);
  700. P:=SearchTopicInHelpFile(HelpFile,Context);
  701. end;
  702. if P=nil then HelpFiles^.FirstThat(@Search);
  703. if P=nil then HelpFile:=nil;
  704. SearchTopicOwner:=HelpFile;
  705. end;
  706. function THelpFacility.LoadTopic(SourceFileID: word; Context: THelpCtx): PTopic;
  707. var P: PTopic;
  708. H: PHelpFile;
  709. begin
  710. if (SourceFileID=0) and (Context=0) then
  711. P:=BuildIndexTopic else
  712. begin
  713. H:=SearchTopicOwner(SourceFileID,Context);
  714. if (H=nil) then P:=nil else
  715. P:=H^.LoadTopic(Context);
  716. end;
  717. LoadTopic:=P;
  718. end;
  719. function THelpFacility.TopicSearch(Keyword: string; var FileID: word; var Context: THelpCtx): boolean;
  720. function ScanHelpFile(H: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
  721. function Search(P: PIndexEntry): boolean; {$ifndef FPC}far;{$endif}
  722. begin
  723. Search:=copy(UpcaseStr(P^.Tag^),1,length(Keyword))=Keyword;
  724. end;
  725. var P: PIndexEntry;
  726. begin
  727. H^.LoadIndex;
  728. P:=H^.IndexEntries^.FirstThat(@Search);
  729. if P<>nil then begin FileID:=H^.ID; Context:=P^.HelpCtx; end;
  730. ScanHelpFile:=P<>nil;
  731. end;
  732. begin
  733. Keyword:=UpcaseStr(Keyword);
  734. TopicSearch:=HelpFiles^.FirstThat(@ScanHelpFile)<>nil;
  735. end;
  736. function THelpFacility.BuildIndexTopic: PTopic;
  737. var T: PTopic;
  738. Keywords: PIndexEntryCollection;
  739. Lines: PUnsortedStringCollection;
  740. procedure InsertKeywordsOfFile(H: PHelpFile); {$ifndef FPC}far;{$endif}
  741. function InsertKeywords(P: PIndexEntry): boolean; {$ifndef FPC}far;{$endif}
  742. begin
  743. Keywords^.Insert(P);
  744. InsertKeywords:=Keywords^.Count>=MaxCollectionSize;
  745. end;
  746. begin
  747. H^.LoadIndex;
  748. if Keywords^.Count<MaxCollectionSize then
  749. H^.IndexEntries^.FirstThat(@InsertKeywords);
  750. end;
  751. procedure AddLine(S: string);
  752. begin
  753. if S='' then S:=' ';
  754. Lines^.Insert(NewStr(S));
  755. end;
  756. var Line: string;
  757. procedure FlushLine;
  758. begin
  759. if Line<>'' then AddLine(Line); Line:='';
  760. end;
  761. var KWCount,NLFlag: sw_integer;
  762. LastFirstChar: char;
  763. procedure NewSection(FirstChar: char);
  764. begin
  765. if FirstChar<=#64 then FirstChar:=#32;
  766. FlushLine;
  767. AddLine('');
  768. AddLine(FirstChar);
  769. AddLine('');
  770. LastFirstChar:=FirstChar;
  771. NLFlag:=0;
  772. end;
  773. function FormatAlias(Alias: string): string;
  774. var StartP,EndP: sw_integer;
  775. begin
  776. repeat
  777. StartP:=Pos(' ',Alias);
  778. if StartP>0 then
  779. begin
  780. EndP:=StartP;
  781. while (EndP+1<=length(Alias)) and (Alias[EndP+1]=' ') do Inc(EndP);
  782. Alias:=copy(Alias,1,StartP-1)+' | '+copy(Alias,EndP+1,High(Alias));
  783. end;
  784. until StartP=0;
  785. if Assigned(HelpFacility) then
  786. if length(Alias)>IndexTabSize-4 then
  787. Alias:=Trim(copy(Alias,1,IndexTabSize-4-2))+'..';
  788. FormatAlias:=Alias;
  789. end;
  790. procedure AddKeyword(KWS: string);
  791. begin
  792. Inc(KWCount); if KWCount=1 then NLFlag:=0;
  793. if (KWCount=1) or
  794. ( (Upcase(KWS[1])<>LastFirstChar) and ( (LastFirstChar>#64) or (KWS[1]>#64) ) ) then
  795. NewSection(Upcase(KWS[1]));
  796. KWS:=FormatAlias(KWS);
  797. if (NLFlag mod 2)=0
  798. then Line:=' '+#2+KWS+#2
  799. else begin
  800. Line:=RExpand(Line,IndexTabSize)+#2+KWS+#2;
  801. FlushLine;
  802. end;
  803. Inc(NLFlag);
  804. end;
  805. var KW: PIndexEntry;
  806. I,p : sw_integer;
  807. IsMultiple : boolean;
  808. MultiCount : longint;
  809. St,LastTag : String;
  810. begin
  811. New(Keywords, Init(5000,5000));
  812. HelpFiles^.ForEach(@InsertKeywordsOfFile);
  813. New(Lines, Init((Keywords^.Count div 2)+100,1000));
  814. T:=NewTopic(0,0,0,'',nil,0);
  815. if HelpFiles^.Count=0 then
  816. begin
  817. AddLine('');
  818. AddLine(' '+msg_nohelpfilesinstalled)
  819. end else
  820. begin
  821. AddLine(' '+msg_helpindex);
  822. KWCount:=0; Line:='';
  823. T^.LinkCount:=Min(Keywords^.Count,MaxBytes div sizeof(T^.Links^[0])-1);
  824. GetMem(T^.Links,T^.LinkSize);
  825. MultiCount:=0;
  826. LastTag:='';
  827. for I:=0 to T^.LinkCount-1 do
  828. begin
  829. KW:=Keywords^.At(I);
  830. if (LastTag<>KW^.Tag^) then
  831. Begin
  832. MultiCount:=0;
  833. IsMultiple:=(I<T^.LinkCount-1) and (KW^.Tag^=Keywords^.At(I+1)^.Tag^);
  834. End
  835. else
  836. IsMultiple:=true;
  837. if IsMultiple then
  838. Begin
  839. Inc(MultiCount);
  840. (* St:=Trim(strpas(pchar(HelpFacility^.LoadTopic(KW^.FileID,KW^.HelpCtx)^.Text))); *)
  841. St:=KW^.Tag^+' ['+IntToStr(MultiCount)+']';
  842. (* { Remove all special chars }
  843. for p:=1 to Length(st) do
  844. if ord(st[p])<=16 then
  845. st[p]:=' ';
  846. p:=pos(KW^.Tag^,St);
  847. if (p=1) then
  848. AddKeyword(St)
  849. else
  850. AddKeyword(KW^.Tag^+' '+St); *)
  851. AddKeyWord(St);
  852. End
  853. else
  854. AddKeyword(KW^.Tag^);
  855. LastTag:=KW^.Tag^;
  856. T^.Links^[I].Context:=longint(KW^.HelpCtx);
  857. T^.Links^[I].FileID:=KW^.FileID;
  858. end;
  859. FlushLine;
  860. AddLine('');
  861. end;
  862. RenderTopic(Lines,T);
  863. Dispose(Lines, Done);
  864. Keywords^.DeleteAll; Dispose(Keywords, Done);
  865. BuildIndexTopic:=T;
  866. end;
  867. function THelpFacility.SearchFile(ID: byte): PHelpFile;
  868. function Match(P: PHelpFile): boolean; {$ifndef FPC}far;{$endif}
  869. begin
  870. Match:=(P^.ID=ID);
  871. end;
  872. begin
  873. SearchFile:=HelpFiles^.FirstThat(@Match);
  874. end;
  875. function THelpFacility.SearchTopicInHelpFile(F: PHelpFile; Context: THelpCtx): PTopic;
  876. var P: PTopic;
  877. begin
  878. if F=nil then P:=nil else
  879. P:=F^.SearchTopic(Context);
  880. SearchTopicInHelpFile:=P;
  881. end;
  882. destructor THelpFacility.Done;
  883. begin
  884. inherited Done;
  885. Dispose(HelpFiles, Done);
  886. end;
  887. END.