diff options
author | Andrew Haines <andrewd207@aol.com> | 2010-08-15 09:22:59 -0400 |
---|---|---|
committer | Andrew Haines <andrewd207@aol.com> | 2010-08-15 09:22:59 -0400 |
commit | 917a2daf4ff769ad27631e6c71a7b919c47e4ecb (patch) | |
tree | c524983404bd70c97c17395995f962cdf41899cc /docview/src | |
parent | 77245bbf79e8568ba143cd7654e8aba352253a81 (diff) | |
parent | 59df247d7a5ff46cc8ac697526510b2ff6cbe5d2 (diff) | |
download | fpGUI-917a2daf4ff769ad27631e6c71a7b919c47e4ecb.tar.xz |
Merge branch 'master' of ssh://fpgui.git.sourceforge.net/gitroot/fpgui/fpgui
Diffstat (limited to 'docview/src')
37 files changed, 13468 insertions, 0 deletions
diff --git a/docview/src/CompareWordUnit.pas b/docview/src/CompareWordUnit.pas new file mode 100644 index 00000000..49c2ea26 --- /dev/null +++ b/docview/src/CompareWordUnit.pas @@ -0,0 +1,107 @@ +Unit CompareWordUnit; + +{$mode objfpc}{$H+} + +// NewView - a new OS/2 Help Viewer +// Copyright 2001 Aaron Lawrence (aaronl at consultant dot com) +// This software is released under the Gnu Public License - see readme.txt + +Interface + +// Compares words and produces a match level (relevance) based +// on the relative sizes etc. Used in searching help files +// to sort by relevance. + +const + // word weightings + mwExactWord = 20; + mwWordStart = 10; + mwWordWithin = 5; + +// Compares the given search word against the given +// reference word. Returns a value indicating how well the +// search word matches, 0 = not at all. +function CompareWord( const SearchWord: string; + const ReferenceWord: string ): longint; + +Implementation + +uses + SysUtils; + +// LOoks for string a within string b, case insensitively +function CaseInsensitivePos( const a, b: string ): longint; +begin + // Budget implementation to begin with. + Result := Pos( UpperCase( a ), UpperCase( b ) ); +end; + +function CompareWord( const SearchWord: string; + const ReferenceWord: string ): longint; +var + OccurrencePos: longint; +begin + Result := 0; + // First up, if the word we're searching for is longer than + // this word, then it can't match at all. + if Length( SearchWord ) > Length( ReferenceWord ) then + exit; + + OccurrencePos := CaseInsensitivePos( SearchWord, ReferenceWord ); + if OccurrencePos = 0 then + // no match. + exit; + + if Length( SearchWord ) = Length( ReferenceWord ) then + begin + // exact word match (except case) + Result := mwExactWord; + exit; + end; + + // partial word match + if OccurrencePos = 1 then + begin + // word starts with searchword + Result := mwWordStart + * Length( SearchWord ) + div Length( ReferenceWord ); + if Result = 0 then + Result := 1; + exit; + end; + + // Matched searchword somewhere within word + Result := mwWordWithin + * Length( SearchWord ) + div Length( ReferenceWord ); + if Result = 0 then + Result := 1; + +end; + +{// Note: searchstring must be uppercase, +function IsMatching( const SearchString: string; + const SearchType: TSearchType; + const Item: string ): boolean; +var + temp: string; +begin + case SearchType of + stStarts: + Result:= StrStarts( SearchString, Item ); + + stContains: + begin + temp:= UpperCase( Item ); + Result:= Pos( SearchString, temp ) <> 0; + end; + + stMatches: + Result:= CompareText( SearchString, + Item )= 0; + end; +end; +} +Initialization +End. diff --git a/docview/src/HelpBitmap.pas b/docview/src/HelpBitmap.pas new file mode 100644 index 00000000..5868f336 --- /dev/null +++ b/docview/src/HelpBitmap.pas @@ -0,0 +1,570 @@ +unit HelpBitmap; + +interface + +// Encapsulates a bitmap as stored in a IPF file. +// Once created from file data they can be used as a normal bitmap. + +uses + Classes, SysUtils, fpg_main, ctypes, + IPFFileFormatUnit; + +type + EHelpBitmapException = class( Exception ); + + + // Lead part of BITMAPARRAYFILEHEADER + INFBITMAPARRAYHEADER = packed record + usType: uint16; // 'BA', 16706 + cbSize: uint32; // Size of the BITMAPARRAYFILEHEADER structure in bytes. + offNext: uint32; // Offset of the next BITMAPARRAYFILEHEADER structure from the start of the file. + cxDisplay: uint16; // Device width, in pels. + cyDisplay: uint16; // Device height, in pels. + end; + + + INFBITMAPHEADER = packed record + // BITMAP FILE HEADER + usType: uint16; // = 'bM' + cbSize: uint32; + xHotspot: uint16; + yHotspot: uint16; + offBits: uint32; // =size(hdr)+size(palette) + // BITMAP INFO HEADER + cbFIx: uint32; // =size(info_hdr) (usually = 12?) + cx: uint16; // width size + cy: uint16; // height size + cPlanes: uint16; // planes, =1 (always seems to be one) + cBitCount: uint16; // bits per pixel + // followed by RGB triples if <= 8bpp + end; + + TRGB = packed record + Blue: uint8; + Green: uint8; + Red: uint8; + end; + + TRGBA = packed record + Blue: uint8; + Green: uint8; + Red: uint8; + Reserved: uint8; + end; + + + THelpBitmap = class( TfpgImage ) + protected + _Header: INFBITMAPHEADER; + _PaletteColorCount: longint; + _pPalette: ^TRGB; + _BitsSize: longint; + FileHandle: TFileStream; + _UncompressedBlockSize: longint; + function GetPaletteSize: longint; + procedure BitmapError(Msg: string); + procedure DecompressLZW(var Buffer: Pointer; const Count: integer; var NewBuffer: PByte; var NewCount: integer); + procedure ReadBitmapData( Blocks: TList; TotalSize: longint ); + public + constructor CreateFromHelpFile(var AFileHandle: TFileStream; Offset: longint); + destructor Destroy; override; + end; + + +var + LZWDecompressBlock: function( pInput: PBYTE; + pOutput: PBYTE; + bytesIn: uint32; + Var bytesOut: uint32; + Var FinalCode: byte ): Boolean; +// APIENTRY; +// 'newview' index 1; + +implementation + +uses + nvUtilities, Math, fpg_imgfmt_bmp; + +const + BFT_bMAP =$4d62; // 'bM' + BFT_BITMAP_ARRAY = $4142; // 'BA' + +type + INFBITMAPDATAHEADER = packed record + ulTotalSize: uint32; + usUncompressedBlockSize: uint16; // bytes per block, after decompression + end; + + TBitmapBlock = class(TObject) + public + _Size: uint16; + _CompressionType: uint8; + _Data: PBYTE; + destructor Destroy; override; + end; + +destructor TBitmapBlock.Destroy; +begin + FreeMem( _Data ); + inherited Destroy; +end; + +constructor THelpBitmap.CreateFromHelpFile(var AFileHandle: TFileStream; Offset: longint); +var + WordsPerLine: longint; + LineSize: longint; + DataHeader: INFBITMAPDATAHEADER; + BytesRead: longint; + + Block: TBitmapBlock; + p: pointer; + Blocks: TList; + BlockIndex: longint; + ImageType: uint16; + BitmapArrayHeader: INFBITMAPARRAYHEADER; + bytes: integer; +begin + inherited Create; + FileHandle := AFileHandle; + + FileHandle.Seek(Offset, soBeginning); + bytes := FileHandle.Read(ImageType, sizeof(ImageType)); + if bytes <> SizeOf(ImageType) then + raise EHelpBitmapException.Create( 'Failed to read ImageType.' ); + + if ImageType = BFT_BITMAP_ARRAY then + begin + // skip array header and read first bitmap only + FileHandle.Seek(Sizeof( BitmapArrayHeader ) - sizeof( ImageType ), soCurrent); + end + else + begin + // skip back over imagetype bytes to read header. + FileHandle.Seek(- sizeof( ImageType ), soCurrent); + end; + + // Read bitmap header + bytes := FileHandle.Read(_Header, SizeOf(_Header)); + if bytes <> SizeOf(_Header) then + raise EHelpBitmapException.Create( 'Failed to read Header.' ); + + // Check it's got a valid type + if _Header.usType <> BFT_bMAP then + raise EHelpBitmapException.Create( 'Invalid bitmap header' ); + + _Header.usType := $4d42; // sibyl only accepts 'BM' not 'bM' + + // We can only parse bitmaps with 1 colour plane + // (I can't be bothered and have never seen bitmaps + // with more than 1 color plane) + if _Header.cPlanes <> 1 then + exit; + + _PaletteColorCount := 0; + if _Header.cBitCount < 24 then + _PaletteColorCount := 1 shl _Header.cBitCount; + + // OS/2 always rounds bitmap rows up to a word: + WordsPerLine := ( _Header.cBitCount * _Header.cx + 31 ) div 32; + LineSize := WordsPerLine * 4; + + // Total size of the bitmap pixel data + _BitsSize := LineSize * _Header.cy; + + // Correct header offset - it is wrong in the header (why?) + _Header.OffBits := sizeof( _Header ) + GetPaletteSize; // TODO: Graeme, double check this! + + // Load palette + if _Header.cBitCount <= 8 then + begin + _pPalette := GetMem( GetPaletteSize ); + bytes := FileHandle.Read(_pPalette, GetPaletteSize); + if bytes <> GetPaletteSize then + raise EHelpBitmapException.Create( 'Failed to read Palette.' ); + end; + + // Read data header + FillChar( DataHeader, sizeof( DataHeader ), 0 ); + bytes := FileHandle.Read(DataHeader, SizeOf(DataHeader)); + if bytes <> SizeOf(DataHeader) then + raise EHelpBitmapException.Create( 'Failed to read DataHeader.' ); + _UncompressedBlockSize := DataHeader.usUncompressedBlockSize; + + // For counting total size, we have already read some bytes: + // the uncompressedblocksize field + BytesRead := sizeof( DataHeader.usUncompressedBlockSize ); + Blocks := TList.Create; + + while BytesRead < DataHeader.ulTotalSize do + begin + Block := TBitmapBlock.Create; + + // Read the block size + FileHandle.Read(Block._Size, SizeOf(Block._Size)); + inc( BytesRead, sizeof( Block._Size ) ); + + // Read the compression type + FileHandle.Read(Block._CompressionType, SizeOf(Block._CompressionType)); + inc( BytesRead, sizeof( Block._CompressionType ) ); + + // since size in the file includes this compression type field, subtract it + dec( Block._Size, sizeof( Block._CompressionType ) ); + + // Now read the block + Block._Data := GetMem( Block._Size ); + FileHandle.Read(Block._Data, Block._Size); + + inc( BytesRead, Block._Size ); + Blocks.Add( Block ); + end; + + ReadBitmapData( Blocks, sizeof( _Header ) + GetPaletteSize + _BitsSize ); + + for BlockIndex := 0 to Blocks.Count - 1 do + begin + Block := TBitmapBlock(Blocks[ BlockIndex ]); + Block.Free; + end; + + Blocks.Free; +end; + +function THelpBitmap.GetPaletteSize: longint; +begin + Result := sizeof( TRGB ) * _PaletteColorCount; +end; + +procedure THelpBitmap.BitmapError(Msg: string); +begin + //Msg:=Msg+' at position '+IntToStr(s.Position); + //if fStartPos>0 then + // Msg:=Msg+'(BitmapPosition='+IntToStr(fStartPos)+')'; + raise EHelpBitmapException.Create(Msg); +end; + +destructor THelpBitmap.Destroy; +begin + FreeMem( _pPalette ); + inherited Destroy; +end; + +procedure THelpBitmap.DecompressLZW(var Buffer: Pointer; const Count: Integer; var NewBuffer: PByte; var NewCount: integer); +type + TLZWString = packed record + Count: integer; + Data: PByte; + end; + PLZWString = ^TLZWString; +const + ClearCode = 256; // clear table, start with 9bit codes + EoiCode = 257; // end of input +var +// NewBuffer: PByte; +// NewCount: PtrInt; + NewCapacity: PtrInt; + SrcPos: PtrInt; + SrcPosBit: integer; + CurBitLength: integer; + Code: Word; + Table: PLZWString; + TableCapacity: integer; + TableCount: integer; + OldCode: Word; + + function GetNextCode: Word; + var + v: Integer; + begin + Result:=0; + // CurBitLength can be 9 to 12 + //writeln('GetNextCode CurBitLength=',CurBitLength,' SrcPos=',SrcPos,' SrcPosBit=',SrcPosBit,' ',hexstr(PByte(Buffer)[SrcPos],2),' ',hexstr(PByte(Buffer)[SrcPos+1],2),' ',hexstr(PByte(Buffer)[SrcPos+2],2)); + // read two or three bytes + if CurBitLength+SrcPosBit>16 then begin + // read from three bytes + if SrcPos+3>Count then BitmapError('LZW stream overrun'); + v:=PByte(Buffer)[SrcPos]; + inc(SrcPos); + v:=(v shl 8)+PByte(Buffer)[SrcPos]; + inc(SrcPos); + v:=(v shl 8)+PByte(Buffer)[SrcPos]; + v:=v shr (24-CurBitLength-SrcPosBit); + end else begin + // read from two bytes + if SrcPos+2>Count then BitmapError('LZW stream overrun'); + v:=PByte(Buffer)[SrcPos]; + inc(SrcPos); + v:=(v shl 8)+PByte(Buffer)[SrcPos]; + if CurBitLength+SrcPosBit=16 then + inc(SrcPos); + v:=v shr (16-CurBitLength-SrcPosBit); + end; + Result:=v and ((1 shl CurBitLength)-1); + SrcPosBit:=(SrcPosBit+CurBitLength) and 7; + //writeln('GetNextCode END SrcPos=',SrcPos,' SrcPosBit=',SrcPosBit,' Result=',Result,' Result=',hexstr(Result,4)); + end; + + procedure ClearTable; + var + i: Integer; + begin + for i:=0 to TableCount-1 do + ReAllocMem(Table[i].Data,0); + TableCount:=0; + end; + + procedure InitializeTable; + begin + CurBitLength:=9; + ClearTable; + end; + + function IsInTable(Code: word): boolean; + begin + Result:=Code<258+TableCount; + end; + + procedure WriteStringFromCode(Code: integer; AddFirstChar: boolean = false); + var + s: TLZWString; + b: byte; + begin + //WriteLn('WriteStringFromCode Code=',Code,' AddFirstChar=',AddFirstChar,' x=',(NewCount div 4) mod IDF.ImageWidth,' y=',(NewCount div 4) div IDF.ImageWidth,' PixelByte=',NewCount mod 4); + if Code<256 then begin + // write byte + b:=Code; + s.Data:=@b; + s.Count:=1; + end else if Code>=258 then begin + // write string + if Code-258>=TableCount then + BitmapError('LZW code out of bounds'); + s:=Table[Code-258]; + end else + BitmapError('LZW code out of bounds'); + if NewCount+s.Count+1>NewCapacity then begin + NewCapacity:=NewCapacity*2+8; + ReAllocMem(NewBuffer,NewCapacity); + end; + System.Move(s.Data^,NewBuffer[NewCount],s.Count); + //for i:=0 to s.Count-1 do write(HexStr(NewBuffer[NewCount+i],2)); // debug + inc(NewCount,s.Count); + if AddFirstChar then begin + NewBuffer[NewCount]:=s.Data^; + //write(HexStr(NewBuffer[NewCount],2)); // debug + inc(NewCount); + end; + //writeln(',WriteStringFromCode'); // debug + end; + + procedure AddStringToTable(Code, AddFirstCharFromCode: integer); + // add string from code plus first character of string from code as new string + var + b1, b2: byte; + s1, s2: TLZWString; + p: PByte; + begin + //WriteLn('AddStringToTable Code=',Code,' FCFCode=',AddFirstCharFromCode,' TableCount=',TableCount,' TableCapacity=',TableCapacity); + // grow table + if TableCount>=TableCapacity then begin + TableCapacity:=TableCapacity*2+128; + ReAllocMem(Table,TableCapacity*SizeOf(TLZWString)); + end; + // find string 1 + if Code<256 then begin + // string is byte + b1:=Code; + s1.Data:=@b1; + s1.Count:=1; + end else if Code>=258 then begin + // normal string + if Code-258>=TableCount then + BitmapError('LZW code out of bounds'); + s1:=Table[Code-258]; + end else + BitmapError('LZW code out of bounds'); + // find string 2 + if AddFirstCharFromCode<256 then begin + // string is byte + b2:=AddFirstCharFromCode; + s2.Data:=@b2; + s2.Count:=1; + end else begin + // normal string + if AddFirstCharFromCode-258>=TableCount then + BitmapError('LZW code out of bounds'); + s2:=Table[AddFirstCharFromCode-258]; + end; + // set new table entry + Table[TableCount].Count:=s1.Count+1; + p:=nil; + GetMem(p,s1.Count+1); + Table[TableCount].Data:=p; + System.Move(s1.Data^,p^,s1.Count); + // add first character from string 2 + p[s1.Count]:=s2.Data^; + // increase TableCount + inc(TableCount); + case TableCount+259 of + 512,1024,2048: inc(CurBitLength); + 4096: BitmapError('LZW too many codes'); + end; + end; + +begin + if Count=0 then exit; + //WriteLn('TFPReaderTiff.DecompressLZW START Count=',Count); + //for SrcPos:=0 to 19 do + // write(HexStr(PByte(Buffer)[SrcPos],2)); + //writeln(); + + NewBuffer:=nil; + NewCount:=0; + NewCapacity:=Count*2; + ReAllocMem(NewBuffer,NewCapacity); + + SrcPos:=0; + SrcPosBit:=0; + CurBitLength:=9; + Table:=nil; + TableCount:=0; + TableCapacity:=0; + try + repeat + Code:=GetNextCode; + //WriteLn('TFPReaderTiff.DecompressLZW Code=',Code); + if Code=EoiCode then break; + if Code=ClearCode then begin + InitializeTable; + Code:=GetNextCode; + //WriteLn('TFPReaderTiff.DecompressLZW after clear Code=',Code); + if Code=EoiCode then break; + if Code=ClearCode then + BitmapError('LZW code out of bounds'); + WriteStringFromCode(Code); + OldCode:=Code; + end else begin + if Code<TableCount+258 then begin + WriteStringFromCode(Code); + AddStringToTable(OldCode,Code); + OldCode:=Code; + end else if Code=TableCount+258 then begin + WriteStringFromCode(OldCode,true); + AddStringToTable(OldCode,OldCode); + OldCode:=Code; + end else + BitmapError('LZW code out of bounds'); + end; + until false; + finally + ClearTable; + ReAllocMem(Table,0); + end; + + ReAllocMem(NewBuffer,NewCount); +// FreeMem(Buffer); +// Buffer:=NewBuffer; +// Count:=NewCount; +end; + + +procedure THelpBitmap.ReadBitmapData( Blocks: TList; + TotalSize: longint ); +var + BytesWritten: longint; + BytesWrittenFromBlock: longword; + BytesRemainingInBlock: longword; + BytesRemainingInBitmap: longword; + FillerBytesRequired: longint; + lastOutByte: byte; + BitmapOutputPointer: PByte; + Block: TBitmapBlock; + BlockIndex: longint; + BitmapData: PBYTE; + ptr: PByte; +begin + // Allocate memory to store the bitmap + Bitmapdata := GetMem( TotalSize ); + + // Copy header to bitmap + MemCopy( _Header, BitmapData, sizeof( _Header ) ); + + // Copy palette into bitmap + ptr := BitmapData + sizeof( _Header ); + MemCopy( _pPalette, ptr, GetPaletteSize ); + + BytesWritten := 0; + + // Point to start writing to bitmap bits. + BitmapOutputPointer := BitmapData + sizeof( _Header ) + GetPaletteSize; + + for BlockIndex := 0 to Blocks.Count - 1 do + begin + Block := TBitmapBlock(Blocks[ BlockIndex ]); + + case Block._CompressionType of + 0,1: // uncompressed (I'm not sure about 1) + begin + MemCopy( Block._Data, BitmapOutputPointer, Block._Size ); + BytesWrittenFromBlock := Block._Size; + inc( BytesWritten, BytesWrittenFromBlock ); + end; + + 2: // LZW compression + begin + // decompress block + if not Assigned( LZWDecompressBlock )then + raise EHelpBitmapException.Create( 'Cannot decode bitmap - DLL not found' ); + +// DecompressLZW(Block._Data, Block._Size); + //LZWDecompressBlock( Block._Data, + // BitmapOutputPointer, + // Block._Size, + // BytesWrittenFromBlock, + // lastOutByte ); + + inc( BytesWritten, BytesWrittenFromBlock ); + + // If the uncompressed data stopped short then + // copy the final code (byte) into remaining bytes + if ( BytesWrittenFromBlock < _UncompressedBlockSize ) + and ( BytesWritten < _BitsSize ) then + begin + BytesRemainingInBlock := _UncompressedBlockSize - BytesWrittenFromBlock; + BytesRemainingInBitmap := _BitsSize - BytesWritten; + + FillerBytesRequired := Min( BytesRemainingInBlock, + BytesRemainingInBitmap ); + + FillMem( BitmapOutputPointer + BytesWrittenFromBlock, + FillerBytesRequired, + LastOutByte ); + inc( BytesWritten, FillerBytesRequired ); + inc( BytesWrittenFromBlock, FillerBytesRequired ); + end; + end; + else + raise EHelpBitmapException.Create( 'Unrecognised bitmap block type' ); + end; // case + + assert( BytesWrittenFromBlock <= _UncompressedBlockSize ); + assert( BytesWritten <= _BitsSize ); + + if ( BitmapOutputPointer + BytesWrittenFromBlock + > BitmapData + TotalSize ) then + assert( false ); + + inc( BitmapOutputPointer, BytesWrittenFromBlock ); + end; + + + AllocateImage(32, _Header.cx, _Header.cy); + if TotalSize <> ImageDataSize then + writeln('Warning: INF Bitmap size and allocated bitmap size are different. ', TotalSize, ' vs ', ImageDataSize); + Move(BitmapData^, ImageData^, TotalSize); + UpdateImage; + + FreeMem( BitmapData, TotalSize ); +end; + + +end. diff --git a/docview/src/HelpFile.pas b/docview/src/HelpFile.pas new file mode 100644 index 00000000..a3801aa5 --- /dev/null +++ b/docview/src/HelpFile.pas @@ -0,0 +1,1247 @@ +unit HelpFile; + +{$mode objfpc}{$H+} + +interface + +// Encapsulates the basic reading of a help file's structure. + +uses + Classes + ,SysUtils + ,fpg_imagelist + ,IPFFileFormatUnit + ,HelpTopic + ,HelpBitmap + ,SearchTable + ; + +type + + TIndexEntry = class(TObject) + private + name: String; + topic: TTopic; + flags: uint8; + public + constructor Create(aName: String; aTopic: TTopic; aFlags: uint8); + destructor Destroy; override; + property getTopic: TTopic read topic; + function getLabel: String; + function isGlobal: boolean; + function getLevel: integer; + end; + + + TIndex = class(TObject) + private + entries: TStringList; + public + constructor Create; + destructor Destroy; override; + function Count: longint; + function GetLabels: TStringList; + function GetTopic(aPos: longint): TTopic; + procedure Add(anIndexEntry: TIndexEntry); + end; + + + THelpFile = class(TObject) + private + function GetStringResourceIDCount: integer; + function GetNumericResourceIDCount: integer; + protected + _Filename : string; + _FileSize : longint; + _Handle: TFileStream; + + _pSlotData: pUInt16; + _SlotDataSize: longint; + + _Title: string; + + _Topics: TList; // of TTopics + + _Dictionary: TStringList; // pointers to strings. + + _Index: TIndex; + + _SearchTable: TSearchTable; + + _ReferencedFiles: TStringList; + + _FontTable: TList; + + _pHeader: TPHelpFileHeader; + _pExtendedHeader: TPExtendedHelpFileHeader; + _pContentsData: pointer; + _pResourceData: pointer; + _pSearchData: pointer; + _pHighlightWords: UInt32ArrayPointer; + _pSlotOffsets: Uint32ArrayPointer; + _pDictionaryData: pointer; + _pFontTableData: pointer; + _pTopicNameData: pointer; + _pTopicGlobalNamesData: pointer; + + procedure InitMembers; + procedure Open; + procedure Close; + procedure ReadFileBlock( Var Dest: pointer; + const StartPosition: LongWord; + const Length: LongWord); + + procedure ReadHeader; + procedure ReadContents; + procedure ReadDictionary; + procedure ReadSearchTable; + + procedure ReadIndex; + + procedure ReadReferencedFilesTable; + procedure ReadFontTableData; + procedure ParseFontTable; + + function GetTopic( Index: longint ): TTopic; + function GetTopicCount: longint; + + function GetDictionaryCount: longint; + function GetDictionaryWord( Index: longint ): string; + + function GetHighlightWords: UInt32ArrayPointer; + + function GetSearchTable: TSearchTable; + + // Lookup global or local panel name list + function FindTopicByName( const Name: string; + Var pData: pointer; + Count: longint; + Offset: longint ): TTopic; + + public + constructor Create( const aFileName: string ); + + destructor Destroy; override; + + function GetIndex: TIndex; + + property Title: string read _Title; + property Topics[ Index: longint ]: TTopic read GetTopic; + property TopicList: TList read _Topics; + property TopicCount: longint read GetTopicCount; + property StringResourceIDCount: integer read GetStringResourceIDCount; + property NumericResourceIDCount: integer read GetNumericResourceIDCount; + property Index: TIndex read GetIndex; + property Filename: string read _FileName; + + property ReferencedFiles: TStringList read _ReferencedFiles; + + procedure GetImages( ImageOffsets: TList; Images: TfpgImageList ); + + function GetImage( ImageOffset: longint ): THelpBitmap; + + property DictionaryCount: longint read GetDictionaryCount; + property DictionaryWords[ Index: longint ]: string read GetDictionaryWord; + + function IndexOfTopic( Topic: TTopic ): longint; + + property SearchTable: TSearchTable read GetSearchTable; + + function FindTopicByResourceID( ID: uint16 ): TTopic; + + function FindTopicByLocalName( const Name: string ): TTopic; + function FindTopicByGlobalName( const Name: string ): TTopic; + + function FindTopicByTitleStartsWith( const SearchText: string ): TTopic; + function FindTopicByTitleContains( const SearchText: string ): TTopic; + + function FindTopicByIndexStartsWith( const SearchText: string ): TTopic; + function FindTopicByIndexContains( const SearchText: string ): TTopic; + + procedure FindResourceIDsForTopic( Topic: TTopic; + ResourceIDs: TList ); + + property HighlightWords: UInt32ArrayPointer read GetHighlightWords; + + property FileSize: longint read _FileSize; + + procedure SetupFontSubstitutes( Substitutions: string ); + public + NotesLoaded: boolean; // used externally + + end; + +// Returns helpfile that the given topic is within +Function TopicFile( Topic: TTopic ): THelpFile; + +function GetHelpFileTitle( const Filename: string ): string; + +Implementation + +uses +// BseErr, +// StringUtilsUnit, +// CharUtilsUnit, +// DebugUnit, +// ACLFileIOUtility, +// ACLLanguageUnit; + fpg_main + ,nvUtilities + ,ACLStringUtility + ; + +// Load "missing" bitmap +{ TODO -oGraeme -cbitmap : Create and load a "missing image" image } +{.$R Images} + +const + FileErrorNotFound = 'File not found '; + FileErrorAccessDenied = 'File access denied'; + FileErrorInUse = 'File in use'; + FileErrorInvalidHeader = 'Invalid file header'; + + + // ----------- + // TIndexEntry + // ----------- + + CONSTRUCTOR TIndexEntry.Create(aName: String; aTopic: TTopic; aFlags: uint8); + begin + LogEvent(LogObjConstDest, 'TIndexEntry.Create'); + name := aName; + topic := aTopic; + flags := aFlags; + end; + + + DESTRUCTOR TIndexEntry.Destroy; + begin + LogEvent(LogObjConstDest, 'TIndexEntry.Destroy'); + topic := nil; + inherited Destroy; + end; + + + FUNCTION TIndexEntry.getLabel: String; + begin + result := name; + + // index level check (level 1 or 2) + if (getLevel) > 1 then + begin + result := '- ' + result; + end; + + if isGlobal then + begin + result := result + ' (g)'; + end; + end; + + + FUNCTION TIndexEntry.isGlobal: boolean; + begin + result := (flags and 64) > 0 + end; + + + FUNCTION TIndexEntry.getLevel: integer; + begin + result := 1; + + // index level check (level 1 or 2) + if (flags and 2 ) > 0 then + begin + result := 2; + end; + end; + + + + + // ----------- + // TIndex + // ----------- + CONSTRUCTOR TIndex.Create; + begin + inherited Create; + + entries := TStringList.Create; + // labels := nil; // lazy + end; + + + DESTRUCTOR TIndex.Destroy; + var + i : longint; + tmpEntry : TIndexEntry; + begin + LogEvent(LogObjConstDest, 'TIndex.Destroy (size:' + IntToStr(entries.Count) + ')'); + + for i := 0 to entries.Count - 1 do + begin + tmpEntry := TIndexEntry(entries.Objects[i]); + if tmpEntry <> nil then + begin + tmpEntry.Free; + entries.Objects[i] := nil; + end; + end; + entries.Free; + + inherited Destroy; + end; + + + FUNCTION TIndex.Count: longint; + begin + result := entries.Count; + end; + + + FUNCTION TIndex.GetLabels: TStringList; + begin + result := entries; + end; + + + FUNCTION TIndex.GetTopic(aPos: longint): TTopic; + begin + result := TIndexEntry(entries.Objects[aPos]).getTopic; + end; + + + PROCEDURE TIndex.add(anIndexEntry: TIndexEntry); + begin +// LogEvent(LogDebug, 'TIndex.add(' + anIndexEntry.getLabel + ', ' + anIndexEntry.ClassName + ')'); + entries.AddObject(anIndexEntry.getLabel, anIndexEntry); + end; + + + + +//Procedure OnLanguageEvent( Language: TLanguageFile; +// const Apply: boolean ); +//var +// tmpPrefix : String; +//begin +// tmpPrefix := 'HelpFile' + LANGUAGE_LABEL_DELIMITER; +// +// Language.LL( Apply, FileErrorNotFound, tmpPrefix + 'FileErrorNotFound', 'File not found' ); +// Language.LL( Apply, FileErrorAccessDenied, tmpPrefix + 'FileErrorAccessDenied', 'Access denied' ); +// Language.LL( Apply, FileErrorInUse, tmpPrefix + 'FileErrorInUse', 'File in use by another program' ); +// Language.LL( Apply, +// FileErrorInvalidHeader, +// tmpPrefix + 'FileErrorInvalidHeader', +// 'File doesn''t appear to be an OS/2 Help document (header ID not correct)' ); +// Language.LL( Apply, +// ErrorCorruptHelpFile, +// tmpPrefix + 'ErrorCorruptHelpFile', +// 'File is corrupt' ); +//end; + +Function TopicFile( Topic: TTopic ): THelpFile; +Begin + Result := Topic.HelpFile as THelpFile; +end; + +function THelpFile.GetStringResourceIDCount: integer; +begin + Result := _pHeader^.nname; +end; + +function THelpFile.GetNumericResourceIDCount: integer; +begin + Result := _pHeader^.nres; +end; + +procedure THelpFile.InitMembers; +begin + _SlotDataSize := 0; + + _pHeader := nil; + _pExtendedHeader := nil; + _pContentsData := nil; + _pSlotOffsets := nil; + _pResourceData := nil; + _pSearchData := nil; + _pDictionaryData := nil; +// _pIndexData := nil; + _pFontTableData := nil; + + _pHighlightWords := nil; + + _Dictionary:= TStringList.Create; + _Topics := TList.Create; +// _Index := TStringList.Create; + _ReferencedFiles := TStringList.Create; + _FontTable := TList.Create; + + NotesLoaded := false; +end; + + +constructor THelpFile.Create(const aFileName: string); +begin + LogEvent(LogObjConstDest, 'THelpFile.Create (file:' + aFileName + ')'); + LogEvent(LogParse, 'Helpfile Load: ' + aFileName); + + _FileName := aFileName; + + InitMembers; + + Open; + + // we always need these basics: + try + ReadHeader; + ReadContents; + ReadDictionary; + ReadFontTableData; + ParseFontTable; + ReadReferencedFilesTable; + except + Close; + raise; + end; + + // the rest is loaded on demand +end; + + +destructor THelpFile.Destroy; +begin + LogEvent(LogObjConstDest, 'THelpFile.Destroy'); + Dispose( _pHeader ); + Dispose( _pExtendedHeader ); + FreeMem( _pContentsData ); + FreeMem( _pSlotOffsets ); + FreeMem( _pResourceData ); + FreeMem( _pSearchData ); + FreeMem( _pDictionaryData ); +// DeallocateMemory( _pIndexData ); + FreeMem( _pFontTableData ); + + FreeMem( _pHighlightWords ); + + // index entries are pointing to topics + // so let us clean them first + if Assigned( _Index ) then + _Index.Free; + + if Assigned( _Topics ) then + DestroyListAndObjects( _Topics ); + + _Dictionary.Free; + _SearchTable.Free; + _ReferencedFiles.Free; + _FontTable.Free; + + _Handle.Free; +end; + +procedure THelpFile.Open; +begin + LogEvent(LogDebug, 'Open File >>'); + if not FileExists( _Filename ) then + raise EHelpFileException.Create( FileErrorNotFound ); + + try + _Handle := TFileStream.Create(_FileName, fmOpenRead or fmShareDenyWrite); + except + on E: Exception do + raise EHelpFileException.Create(E.Message); + end; + //case rc of + // ERROR_FILE_NOT_FOUND: // crap, this doesn't actually occur! + // raise EHelpFileException.Create( FileErrorNotFound ); + // + // ERROR_ACCESS_DENIED: + // raise EHelpFileException.Create( FileErrorAccessDenied ); + // + // ERROR_SHARING_VIOLATION: + // raise EHelpFileException.Create( FileErrorInUse ); + // + // else + // raise EHelpFileException.Create( SysErrorMessage( rc ) ); + //end; + + _FileSize := GetFileSize(_Filename); + LogEvent(LogDebug, 'Open File <<'); +end; + +procedure THelpFile.Close; +begin + _Handle.Free; + _Handle := nil; +end; + +procedure THelpFile.ReadFileBlock(var Dest: pointer; + const StartPosition: LongWord; const Length: LongWord); +var + bytes: LongWord; +begin + if Length = 0 then + exit; // nothing to read - go home! + + _Handle.Seek(StartPosition, soBeginning); + + // we allocate early so this should never happen + if Dest = nil then + Dest := GetMem(Length); + + bytes := _Handle.Read(Dest^, Length); + if bytes <> Length then + raise EHelpFileException.Create(ErrorCorruptHelpFile); +end; + +procedure THelpFile.ReadHeader; +begin + LogEvent(LogParse, 'Read header'); + New(_pHeader); + ReadFileBlock( _pHeader, + 0, + sizeof( THelpFileHeader ) ); + if _pHeader^.ID <> INF_HEADER_ID then + begin + // not an OS/2 help file. + if (Byte(_pHeader^.ID[0]) = $5f) and (Byte(_pHeader^.ID[1]) = $3f) then + raise EWindowsHelpFormatException.Create( 'It seems we have a Win16 help file!' ); + + raise EHelpFileException.Create( FileErrorInvalidHeader ); + end; + + _Title := _pHeader^.Title; + + if _pHeader^.extstart > 0 then + begin + New(_pExtendedHeader); + // read extended header + ReadFileBlock( _pExtendedHeader, + _pHeader^.extstart, + sizeof( _pExtendedHeader^ ) ); + end; +end; + +procedure THelpFile.ReadContents; +var + Topic: TTopic; + EntryIndex: longint; + pEntry: pTTOCEntryStart; + pEnd: pbyte; + tocarray: UInt32ArrayPointer; + pData: Pointer; + p: PByte; +begin + LogEvent(LogParse, 'Read contents'); + + if _pHeader^.ntoc = 0 then + exit; // explicit check required since ntoc is unsigned + + // Presize the topics list to save reallocation time + _Topics.Capacity := _pHeader^.ntoc; + + // read toc offsets array + //ReadFileBlock( tocarray, + // _pHeader^.tocoffsetsstart, + // _pHeader^.ntoc * SizeOf(uint32) ); + + // read slots first so that Topics can refer to it. + ReadFileBlock( _pSlotOffsets, + _pHeader^.slotsstart, + _pHeader^.nslots * sizeof( uint32 ) ); + + ReadFileBlock( _pContentsData, + _pHeader^.tocstart, + _pHeader^.toclen ); + + pEntry := _pContentsData; + pEnd := _pContentsData + _pHeader^.toclen; + p := PByte(pEntry); + + for EntryIndex := 0 to _pHeader^.ntoc - 1 do + begin +// pEntry := _Handle.Seek(tocarray[EntryIndex], soBeginning); +// pEntry := tocarray[EntryIndex]; + if p >= pEnd then + // runs off end of data! + raise EHelpFileException.Create( ErrorCorruptHelpFile ); + + Topic := TTopic.Create( _Handle, + _pSlotOffsets, + _Dictionary, + pEntry, + _FontTable, + _ReferencedFiles ); + + Topic.HelpFile := Self; + Topic.Index := EntryIndex; + + _Topics.Add( Topic ); + p := PByte(pEntry); + inc(p, pEntry^.Length); + pEntry := pTTOCentryStart(p); + end; +end; + +procedure THelpFile.ReadDictionary; +var + i: longint; + Len: uint8; + p: pbyte; + pEnd: pbyte; + s: string; + c: array[0..255] of char; +begin + LogEvent(LogParse, 'Read dictionary'); + + if _pHeader^.ndict = 0 then + exit; // explicit check required since ndict is unsigned + + ReadFileBlock( _pDictionaryData, + _pHeader^.dictstart, + _pHeader^.dictlen ); + + P := _pDictionaryData; + pEnd := _pDictionaryData + _pHeader^.dictlen; + + // Presize the dictionary to save reallocation + _Dictionary.Capacity := _pHeader^.ndict; + for i := 0 to _pHeader^.ndict - 1 do + begin + // adjust length so we can use as a Pascal string + // (file uses length including length byte, + // Pascal string have length excluding length byte) + if p >= pEnd then + // ran off end of data + raise EHelpFileException.Create( 'Error reading help file dictionary' ); + + + FillChar(c, sizeof(c), 0); // fill string with NUL chars + Len := p^ - 1; // read string length value (corrected length) + Inc(p, sizeof(byte)); // move pointer + Move(p^, c, Len); // read string of dictionary + s := c; // convert PChar to String type + + _Dictionary.Add( s ); + Inc(p, Len); // move pointer to next item + end; +end; + + +function THelpFile.GetIndex: TIndex; +begin + if _Index = nil then + begin + ReadIndex; + end; + Result := _Index; +end; + +type + TIndexEntryHeader = packed record + TextLength: uint8; + Flags: uint8; + NumberOfRoots: uint8; + TOCIndex: uint16; + end; + pTIndexEntryHeader = ^TIndexEntryHeader; + +procedure THelpFile.ReadIndex; +var + IndexIndex: longint; + pEntryHeader: pTIndexEntryHeader; + EntryText: string; + IndexTitleLen: longint; + p: pByte; + pEnd: pByte; + pIndexData: pointer; + tmpIndexEntry: TIndexEntry; +begin + LogEvent(LogParse, 'Read index'); + _Index := TIndex.Create; + if _pHeader^.nindex = 0 then + exit; // explicit check required since ndict is unsigned + + pIndexData := nil; + ReadFileBlock( pIndexData, + _pHeader^.indexstart, + _pHeader^.indexlen ); + + P := pIndexData; + pEnd := pIndexData + _pHeader^.indexlen; + + for IndexIndex := 0 to _pHeader^.nindex - 1 do + begin + if p >= pEnd then + // ran off end of data + raise EHelpFileException.Create( 'Error reading help file index' ); + + pEntryHeader := pTIndexEntryHeader(p); + IndexTitleLen := pEntryHeader^.TextLength; + inc( p, sizeof( TIndexEntryHeader ) ); + + EntryText := ''; + SetString(EntryText, PChar(p), IndexTitleLen); + + if pEntryHeader^.TOCIndex < _Topics.Count then + begin + tmpIndexEntry := TIndexEntry.Create(EntryText, TTopic(_Topics[pEntryHeader^.TOCIndex]), pEntryHeader^.flags); + _Index.Add(tmpIndexEntry); + end + else +// raise EHelpFileException.Create( 'Error reading help file index - out of range topic reference' ); + ; // pass! something special + + inc( p, IndexTitleLen + + pEntryHeader^.NumberOfRoots + * sizeof( uint32 ) ); // skip 'roots' for index search + end; + + FreeMem( pIndexData ); +end; + +function THelpFile.GetSearchTable: TSearchTable; +begin + if _SearchTable = nil then + ReadSearchTable; + Result := _SearchTable; +end; + +procedure THelpFile.ReadSearchTable; +var + SearchTableOffset: longint; + SearchTableRecordLengthIs16Bit: boolean; +begin + LogEvent(LogParse, 'Read search table'); + + if _pHeader^.SearchLen = 0 then + begin + LogEvent(LogParse, 'Read search table (len = 0'); + exit; + end; + + SearchTableOffset := _pHeader^.SearchStart and $7fffffff; + SearchTableRecordLengthIs16Bit := _pHeader^.SearchStart and $80000000 > 0; + ReadFileBlock( _pSearchData, + SearchTableOffset, + _pHeader^.SearchLen ); + + _SearchTable := TSearchTable.Create( _pSearchData, + SearchTableRecordLengthIs16Bit, + _Dictionary.Count, + _Topics.Count ); +end; + +function THelpFile.GetHighlightWords: UInt32ArrayPointer; +begin + if _pHighlightWords = nil then + _pHighlightWords := GetMem( _Dictionary.Count * sizeof( UInt32 ) ); + Result := _pHighlightWords; +end; + +function THelpFile.FindTopicByResourceID( ID: uint16 ): TTopic; +var + i: longint; + pResourceIDs: UInt16ArrayPointer; + pTopicIndices: UInt16ArrayPointer; + FileResourceID: uint16; + TopicIndex: uint16; +begin + Result := nil; + + if _pHeader^.nres = 0 then + // since nres is unsigned + exit; + + if _pResourceData = nil then + begin + ReadFileBlock( _pResourceData, + _pHeader^.resstart, + (_pHeader^.nres * sizeof( uint16 )) * 2 ); // list of IDs, list of topics + end; + + pResourceIDs := _pResourceData; + pTopicIndices := _pResourceData + + _pHeader^.nres * sizeof( uint16 ); + + for i := 0 to _pHeader^.nres - 1 do + begin + FileResourceID := pResourceIDs^[ i ]; + if FileResourceID = ID then + begin + // found + TopicIndex := pTopicIndices^[ i ]; + Result := TTopic(_Topics[ TopicIndex ]); + exit; + end; + end; +end; + +// Look up a local "panel name" and return associated topic, if any. +function THelpFile.FindTopicByLocalName( const Name: string ): TTopic; +begin + Result := FindTopicByName( Name, + _pTopicNameData, + _pHeader^.nname, + _pHeader^.namestart ); +end; + +function THelpFile.FindTopicByGlobalName( const Name: string ): TTopic; +begin + Result := nil; + + if _pExtendedHeader = nil then + // no extended header - no global list to lookup + exit; + + Result := FindTopicByName( Name, + _pTopicGlobalNamesData, + _pExtendedHeader ^. EntryInGNameTable, + _pExtendedHeader ^. HelpPanelGNameTblOffset ); + +end; + +// The text of the names are stored in the (global) dictionary +// with a table referencing them. +// We could use a binary search here... but whatever... +function THelpFile.FindTopicByName( const Name: string; + Var pData: pointer; + Count: longint; + Offset: longint ): TTopic; +var + i: longint; + pNameTable: UInt16ArrayPointer; + pTopicIndices: UInt16ArrayPointer; + TopicIndex: uint16; + + TopicNameWordIndex: uint16; + TopicName: string; +begin + Result := nil; + + if Count = 0 then + // since it's unsigned + exit; + + if pData = nil then + ReadFileBlock( pData, + Offset, + Count * sizeof( uint16 ) * 2 ); // list of name words, list of topics + + // get pointers to the two parts of the table + pNameTable := pData; + pTopicIndices := pData + + Count * sizeof( uint16 ); + + for i := 0 to Count - 1 do + begin + TopicNameWordIndex := pNameTable^[ i ]; + TopicName := DictionaryWords[ TopicNameWordIndex ]; + + if CompareText( TopicName, Name ) = 0 then + begin + // found + TopicIndex := pTopicIndices^[ i ]; + Result := TTopic(_Topics[ TopicIndex ]); + exit; + end; + end; +end; + + +// TODO move to index class +function THelpFile.FindTopicByIndexStartsWith( const SearchText: string ): TTopic; +var + i: longint; + tmpLabel: String; +begin + result := nil; + GetIndex; // make sure it's read + + for i := 0 to _Index.Count - 1 do + begin + tmpLabel := _Index.GetLabels[i]; + if SameText(tmpLabel, SearchText) then + begin + // found + result := Index.getTopic(i); + exit; + end; + end; +end; + + +function THelpFile.FindTopicByIndexContains(const SearchText: string): TTopic; +var + i: longint; + tmpLabel: String; +begin + result := nil; + GetIndex; // make sure it's read + + for i := 0 to _Index.Count - 1 do + begin + tmpLabel := _Index.GetLabels[i]; + if Pos(UpperCase(SearchText), UpperCase(tmpLabel)) > 0 then + begin + // found + result := Index.getTopic(i); + exit; + end; + end; +end; + + +function THelpFile.FindTopicByTitleStartsWith( const SearchText: string ): TTopic; +var + i: longint; + tmpTopic: TTopic; + tmpLevel : integer; + tmpMore : boolean; +begin + result := nil; + + tmpLevel := 0; + repeat + tmpMore := false; + inc(tmpLevel); + for i := 0 to _Topics.Count - 1 do + begin + tmpTopic := TTopic(_Topics[i]); + if tmpLevel = tmpTopic.ContentsLevel then + begin + if StrStartsWithIgnoringCase(tmpTopic.Title, SearchText) then + begin + result := tmpTopic; + exit; + end; + end; + if tmpLevel < tmpTopic.ContentsLevel then + begin + tmpMore := True; + end; + end; + until NOT tmpMore; +end; + +function THelpFile.FindTopicByTitleContains( const SearchText: string ): TTopic; +var + i: longint; + tmpTopic: TTopic; + tmpLevel : integer; + tmpMore : boolean; +begin + result := nil; + + tmpLevel := 0; + repeat + tmpMore := false; + inc(tmpLevel); + for i := 0 to _Topics.Count - 1 do + begin + tmpTopic := TTopic(_Topics[i]); + if tmpLevel = tmpTopic.ContentsLevel then + begin + if CaseInsensitivePos( SearchText, tmpTopic.Title) > 0 then + begin + result := tmpTopic; + exit; + end; + end; + if tmpLevel < tmpTopic.ContentsLevel then + begin + tmpMore := True; + end; + end; + until NOT tmpMore; +end; + +procedure THelpFile.FindResourceIDsForTopic( Topic: TTopic; + ResourceIDs: TList ); +var + i: longint; + pResourceIDs: UInt16ArrayPointer; + pTopicIndices: UInt16ArrayPointer; +begin + ResourceIDs.Clear; + + if _pHeader^.nres = 0 then + // since nres is unsigned + exit; + + if _pResourceData = nil then + ReadFileBlock( _pResourceData, + _pHeader^.resstart, + _pHeader^.nres * sizeof( uint16 ) * 2 ); // list of IDs, list of topics + + pResourceIDs := _pResourceData; + pTopicIndices := _pResourceData + + _pHeader^.nres * sizeof( uint16 ); + + for i := 0 to _pHeader^.nres - 1 do + begin + if pTopicIndices^[ i ] = Topic.Index then + begin + // found + ResourceIDs.Add( pointer( pResourceIDs^[ i ] ) ); + end; + end; +end; + +procedure THelpFile.ReadReferencedFilesTable; +var + i: longint; + p: pointer; + pData: pointer; + DatabaseName: string; + pLength: pByte; +begin + if _pExtendedHeader = nil then + // no extended header -> no referenced files table + exit; + + if _pExtendedHeader^.Numdatabase = 0 then + exit; + + pData := nil; // please allocate... + ReadFileBlock( pData, + _pExtendedHeader^.DatabaseOffset, + _pExtendedHeader^.DatabaseSize ); + + p := pData; + for i := 0 to _pExtendedHeader^.Numdatabase - 1 do + begin + pLength := p; // length byte, including itself + SetString(DatabaseName, p+1, pLength^-1); // use length value minus the length byte to get the string length + _ReferencedFiles.Add( DatabaseName ); + inc( p, pLength^ ); // skip to next entry using full length (including length byte) + end; + FreeMem( pData ); +end; + +procedure THelpFile.ReadFontTableData; +begin + if _pExtendedHeader = nil then + // no extended header -> no font table + exit; + + if _pExtendedHeader^.NumFontEntry = 0 then + exit; + + ReadFileBlock( _pFontTableData, + _pExtendedHeader^.FontTableOffset, + _pExtendedHeader^.NumFontEntry * sizeof( THelpFontSpec ) ); +end; + +procedure THelpFile.ParseFontTable; +var + i: longint; + p: pointer; + pFontSpec: pTHelpFontSpec; +begin + _FontTable.Clear; + + p := _pFontTableData; + if p = nil then + exit; // no data + + for i := 0 to _pExtendedHeader^.NumFontEntry - 1 do + begin + pFontSpec := p + i * sizeof( THelpFontSpec ); + _FontTable.Add( pFontSpec ); + end; +end; + +procedure THelpFile.GetImages( ImageOffsets: TList; Images: TfpgImageList ); +var + ListIndex: longint; + ImageOffset: longint; + Bitmap: THelpBitmap; +begin + Images.Clear; + + for ListIndex := 0 to ImageOffsets.Count - 1 do + begin + ImageOffset := longint( ImageOffsets[ ListIndex ] ); + try + Bitmap := THelpBitmap.CreateFromHelpFile( _Handle, + _pHeader^.imgstart + + ImageOffset ); + except + on e: EHelpBitmapException do +{ raise EHelpFileException.Create( 'Error loading help bitmap at' + + IntToStr( ImageOffset ) + + ': ' + + e.Message );} + begin +// Bitmap := THelpBitmap.Create; + Bitmap := THelpBitmap(fpgImages.GetImage('stdimg.dlg.critical')); +// Bitmap.LoadFromResourceName( 'MissingBitmap' ); // TODO: Add image resource to DocView + end; + end; + + Images.AddImage(Bitmap); + end; +end; + +function THelpFile.GetImage( ImageOffset: longint ): THelpBitmap; +begin + try + Result := THelpBitmap.CreateFromHelpFile( _Handle, + _pHeader^.imgstart + + ImageOffset ); + except + on e: EHelpBitmapException do + begin + result := nil; + raise EHelpFileException.Create( 'Error loading help bitmap at' + + IntToStr( ImageOffset ) + + ': ' + + e.Message ); + end; + end; +end; + +function THelpFile.GetTopic( Index: longint ): TTopic; +begin + if ( Index < 0 ) + or ( Index > _Topics.Count - 1 ) then + Result := nil + else + Result := TTopic(_Topics[ Index ]); +end; + +function THelpFile.GetTopicCount: longint; +begin + Result := _Topics.Count; +end; + +function THelpFile.IndexOfTopic( Topic: TTopic ): longint; +begin + Result := _Topics.IndexOf( Topic ); +end; + +function THelpFile.GetDictionaryCount: longint; +begin + Result := _Dictionary.Count; +end; + +function THelpFile.GetDictionaryWord( Index: longint ): string; +begin + Result := _Dictionary[ Index ]; +end; + + +// Looks for fonts that should be substitued to the +// users selected fixed font +// doesn't make a lot of sense for this to be here... +procedure THelpFile.SetupFontSubstitutes( Substitutions: string ); +var + Item: string; + FontName: string; + SpacePos: longint; + W: longint; + H: longint; + i: longint; + pFontSpec: pTHelpFontSpec; + tmpSubstitutionItems : TStrings; + tmpCounter : integer; + tmpDimensionParts : TStrings; + s: string; + PointSize: word; + cp: integer; +begin + ParseFontTable; // (re)load table from raw data + + tmpSubstitutionItems := TStringList.Create; + StrExtractStrings(tmpSubstitutionItems, Substitutions, [';'], #0); + + for tmpCounter := 0 to tmpSubstitutionItems.Count - 1 do + begin + Item := tmpSubstitutionItems[tmpCounter]; + try + if Item <> '' then + begin + // Look for space in xxxx WxH + SpacePos := LastDelimiter(' ', Item); + if SpacePos > 0 then + begin + // fontname comes before + FontName := StrLeft( Item, SpacePos - 1 ); + Delete( Item, 1, SpacePos ); + + // width and height after, with an X between + tmpDimensionParts := TStringList.Create; + StrExtractStrings(tmpDimensionParts, Item, ['x'], #0); + W := StrToInt(tmpDimensionParts[0]); + H := StrToInt(tmpDimensionParts[1]); + tmpDimensionParts.Destroy; + if ( W > 0 ) and ( H > 0 ) then + begin + // Now look through the font table for matches + for i := 0 to _FontTable.Count - 1 do + begin + pFontSpec := _FontTable[ i ]; + cp := pFontSpec^.Codepage; + s := StrNPas( pFontSpec^.FaceName, sizeof( pFontSpec^.FaceName ) ); + if s = FontName then + begin + // same face name... + // this formula seems to give a simulated pointsize compared to + // what the original VIEW program intended. + PointSize := (pFontSpec^.Height * 2) div 3; + if ( H = PointSize ) then + begin + // match + pFontSpec^.Codepage := High(word); // font substitute marker added +// _FontTable[ i ] := SubstituteFixedFont; + end; + end; + end; + end; + end; + end; + except + end; + end; + + tmpSubstitutionItems.Free; +end; + + +// ------------------------------------------------------------- +// Get the title only from specific help file (if possible) + +function GetHelpFileTitle( const Filename: string ): string; +var + Header: THelpFileHeader; + fstream: TFileStream; + Ext: string; +begin + Ext := ExtractFileExt( Filename ); + Result := ''; + + if SameText( Ext, '.inf' ) + or SameText( Ext, '.hlp' ) then + begin + try + try + fstream := TFileStream.Create(Filename, fmOpenRead); + fstream.Position := 0; + FillChar( Header, sizeof( Header ), 0 ); + fstream.Read(Header, SizeOf(Header)); + if Header.ID = INF_HEADER_ID then + Result := StrPas(Header.title); + except + // silently ignore errors - it's not to critical at this point. + end; + finally + fstream.Free; + end; + end; +end; + + +end. + diff --git a/docview/src/HelpTopic.pas b/docview/src/HelpTopic.pas new file mode 100644 index 00000000..a2ae38dc --- /dev/null +++ b/docview/src/HelpTopic.pas @@ -0,0 +1,2695 @@ +Unit HelpTopic; + +{$mode objfpc}{$H+} + +Interface + +// This is it - the monster which decodes IPF data. +// It's created with a reference to the contents data defining it. +// It gets relevant pointers out of that. When GetText is called +// it decodes the data and spits out formatted text to suit +// RichTextView. + +uses + Classes, + HelpWindowDimensions, + IPFFileFormatUnit; + +const + DefaultGroupIndex = 0; + + RTF_NewLine = #10; + +var + { TODO -oGraeme -cPointers : I don't like this - double check alternatives later } + // placeholder for font table entry, indiciating user fixed font should be substituted + SubstituteFixedFont: pointer = Pointer(1); + +type + THelpLink = class(TObject) + public + HelpFile: TObject; // file this link is within + // Even though it doesn't do anything, + // we have to have a constructor to allow + // virtual constructors to work + constructor Create; virtual; + end; + + + THelpTopicSlot = class(TObject) + public + pData: pUInt8; // Pointer to actual Slot structure in INF file. + Size: longint; // Number of bytes in the text for this Slot (slotheader.ntext) + pLocalDictionary: UInt16ArrayPointer; // Pointer to Slot's local dictionary + LocalDictSize: uint8; // Number of entries in the local dictionary + destructor Destroy; override; + end; + + + THelpLinkClass = class of THelpLink; + + + TFootnoteHelpLink = class(THelpLink) + public + TopicIndex: longint; + Title: string; // from text within link + end; + + + TWindowedHelpLink = class(THelpLink) + public + GroupIndex: longint; // DefaultGroupIndex if not specified. + // Note: Overrides contents group index of topic + Automatic: boolean; // link should be automatically followed on topic display + Split: boolean; // link should open the window within the parent + ViewPort: boolean; // link should always open a new window + Dependent: boolean; // window opened by link should be closed + // when current topic is closed + Rect: THelpWindowRect; // Display window with this rectangle. + // Note: overrides contents rect + constructor Create; override; + destructor Destroy; override; + end; + + + TInternalHelpLink = class(TWindowedHelpLink) + public + TopicIndex: longint; + end; + + + THelpLinkByResourceID = class(TWindowedHelpLink) + public + ResourceID: longint; + end; + + + SlotArray = array[0..0] of THelpTopicSlot; + pSlotArray = ^SlotArray; + + + TFontState = (fsNormal, fsFixed, fsCustom); + TIPFTextAlignment = (itaLeft, itaRight, itaCenter, itaCenterOnePara); + + + TParseState = record + Alignment: TIPFTextAlignment; + ForegroundColorTag: string; + BackgroundColorTag: string; + Spacing: boolean; + FontState: TFontState; + InCharGraphics: boolean; + LinkIndex: longint; + StartOfTextBlock: longint; + TextBlock: string; + FootnoteLink: TFootnoteHelpLink; + StyleCode: longint; + end; + + + TTopic = class(TObject) + protected + _FileHandle: TFileStream; + _pTOCEntry: pTTOCEntryStart; + _pSlotOffsets: UInt32ArrayPointer; + _Slots: TList; + _pSlotNumbers: puint16; + _NumSlots: longint; + _Title: string; + _GlobalDictionary: TStringList; + _ShowInContents: boolean; + _ContentsLevel: integer; + _ContentsGroupIndex: longint; + _FontTable: TList; + _ReferencedFiles: TStrings; + procedure SetTitle( const NewValue: string ); + function GetTitle: string; + + // Returns the tag texts for the given bitmap ref + function GetImageText( CurrentAlignment: TIPFTextAlignment; + BitmapOffset: longint; + BitmapFlags: longint; + ImageOffsets: TList ): string; + + Procedure ProcessLinkedImage( Var State: TParseState; + Var pData: pByte; + Var OutputString: string; + ImageOffsets: TList ); + procedure TranslateIPFEscapeCode( Var State: TParseState; + Var pData: pUInt8; + var AText: String; + Var WordsOnLine: longint; + ImageOffsets: TList ); + + function CreateLink( Var LinkIndex: longint; + Var Link: THelpLink; + LinkClass: THelpLinkClass ): boolean; + + procedure EnsureSlotsLoaded; + + // returns true if the escape code at pData results in whitespace. + function IPFEscapeCodeSpace( Var State: TParseState; Var pData: pUInt8 ): boolean; + + function GetNextIPFTextItem( Var SlotIndex: longint; + Var pData: pUInt8; + Var State: TParseState ): longint; + + function CheckForSequence( WordSequences: TList; + SlotIndex: longint; + pData: pUint8; + State: TParseState; + GlobalDictIndex: longint + ): longint; + + public + HelpFile: TObject; + Index: longint; + SearchRelevance: longint; + Links: TList; // only valid after GetText + constructor Create( var FileHandle: TFileStream; + pSlotOffsets: UInt32ArrayPointer; + Dictionary: TStringList; + var pTOCEntry: pTTOCEntryStart; + FontTable: TList; + ReferencedFiles: TStrings ); + destructor Destroy; override; + property Title: string read GetTitle write SetTitle; + procedure SetTitleFromMem( const p: pointer; const Len: byte ); + // Main function for retrieving text for topic. + // HighlightSequences: list of sequences to highlight + // if nil then ignored. + // ShowCodes: indicates debugging: hex output of escape + // codes will be included + // ShowWordSeparators: | will be included after each dictionary + // word inserted + // Text: The output is written to here. IS NOT CLEARED FIRST. + // ImageOffsets: For each image that occurs in the text, + // the help file offset will be written to this list. + // HighlightMatches: if not nil, and HighlightSequences is not nil, + // will return offsets to each highlight match + procedure GetText( HighlightSequences: TList; + ShowCodes: boolean; + ShowWordSeparators: boolean; + var Text: String; + ImageOffsets: TList; + HighlightMatches: TList ); + // if StopAtFirstOccurrence true, returns 0 or 1 + // if false, returns count of occurrences of word + function SearchForWord( DictIndex: integer; + StopAtFirstOccurrence: boolean ): longint; + // searches for sequences out of those listed in WordSequence + // Each element of WordSequence contains a pointer to an array + // of flags for each dictionary word, indicating whether that word + // is to be a possible match. + function SearchForWordSequences( WordSequence: TList; StopAtFirstOccurrence: boolean ): longint; + procedure GetContentsWindowRect( ContentsRect: THelpWindowRect ); + // search for binary data including codes + function SearchForData( Data: pbyte; DataLen: integer ): boolean; + procedure SaveIPFEscapeCode( Var State: TParseState; + Var pData: pUInt8; + Var F: TextFile; + ImageOffsets: TList ); + procedure SaveToIPF( Var f: TextFile; ImageOffsets: TList ); + property ShowInContents: boolean read _ShowInContents; + property ContentsLevel: integer read _ContentsLevel; + property ContentsGroupIndex: longint read _ContentsGroupIndex; + function CountWord( DictIndex: integer ): longint; + function ContainsWord( DictIndex: integer ): boolean; + end; + + +// Compares two topics for purposes of sorting by +// search match relevance +function TopicRelevanceCompare( Item1, Item2: pointer ): longint; + +// Compares two topics for purposes of sorting by title +function TopicTitleCompare( Item1, Item2: pointer ): longint; + + +implementation + + +uses + SysUtils + ,NewViewConstantsUnit + ,nvUtilities + ,ACLStringUtility + ,SettingsUnit + ; + +const + IPFColors: array[ 0..15 ] of string = + ( + //rrggbb + '', // default + '#0000ff', // blue + '#ff0000', // red + '#ff00ff', // pink (purple) + '#00ff00', // green + '#00ffff', // cyan + '#ffff00', // yellow + '#808000', // neutral = brown + '#404040', // dark gray + '#000080', // dark blue + '#800000', // dark red + '#800080', // dark pink (purple) + '#008000', // dark green + '#008080', // dark cyan + '#000000', // black + '#c0c0c0' // pale gray + ); + + // for ecHighlight1 + IPFHighlight1Tags : array [ 0..6 ] of string = + ( + '</i></b></u></color>', // normal + '<i>', // hp1 italitc + '<b>', // hp2 bold + '<b><i>', // hp3 bold italic + '<u>', // hp5 underline + '<u><i>', // hp6 underline italic + '<u><b>' // hp7 underline bold + ); + + // for ecHighlight2 + IPFHighlight2Tags : array [ 0..3 ] of string = + ( + '</i></b></u></color>', // normal + '<color blue>', // hp4 blue + '<color red>', // hp8 red + '<color purple>' // hp9 purple + ); + + BlankString: string = ''; + +var + DefaultTitle: string; + + +function GetBeginLink( LinkIndex: longint ): string; +begin + Result := '<link ' + IntToStr( LinkIndex ) + '>' +end; + +function GetEndLinkTags( const State: TParseState ): string; +begin + Result := '</link>' + State.ForegroundColorTag; +end; + +// Even though it doesn't do anything, +// we have to have a constructor to allow +// virtual constructors to work +constructor THelpLink.Create; +begin + // do nothing +end; + +constructor TWindowedHelpLink.Create; +begin + GroupIndex := DefaultGroupIndex; + Automatic := false; + ViewPort := false; + Dependent := false; + Rect := THelpWindowRect.Create; +end; + +destructor TWindowedHelpLink.Destroy; +begin + Rect.Destroy; +end; + +destructor THelpTopicSlot.Destroy; +begin + { TODO -ograeme -ccleanup memory : Double check this } + FreeMem(pData);// DeallocateMemory( pData ); + FreeMem(pLocalDictionary); // DeallocateMemory( pLocalDictionary ); +end; + +constructor TTopic.Create( var FileHandle: TFileStream; + pSlotOffsets: UInt32ArrayPointer; + Dictionary: TStringList; + var pTOCEntry: pTTOCEntryStart; + FontTable: TList; + ReferencedFiles: TStrings ); +var + pExtendedInfo: pExtendedTOCEntry; + titleLen: integer; + XY: THelpXYPair; + p: pbyte; + Flags: byte; +begin + _FileHandle := FileHandle; + _pSlotOffsets := pSlotOffsets; + + _Title := ''; + _GlobalDictionary := Dictionary; + _ContentsGroupIndex := 0; + + _pTOCEntry := pTOCEntry; + _NumSlots := pTOCEntry^.numslots; + + Flags := _pTOCEntry^.flags; + p := pByte( _pTOCEntry ) + sizeof( TTOCEntryStart ); + + if ( Flags and TOCEntryExtended ) = TOCEntryExtended then + begin + pExtendedInfo := pExtendedTOCEntry( p ); + inc( p, sizeof( TExtendedTOCEntry ) ); + + if ( pExtendedInfo^.w1 and 1 ) > 0 then + // skip position + inc( p, sizeof( XY ) ); + + if ( pExtendedInfo^.w1 and 2 ) > 0 then + // skip size + inc( p, sizeof( XY ) ); + + if ( pExtendedInfo^.w1 and 8 ) > 0 then + // skip window controls + inc( p, sizeof(word) ); // increment by 2 + + if ( pExtendedInfo^.w1 and $40 ) > 0 then + // skip something else, unknown... style? 2 bytes + inc( p, sizeof(word) ); // increment by 2 + + if ( pExtendedInfo^.w2 and 4 ) > 0 then + begin + _ContentsGroupIndex := pUInt16(p)^; + // read group + inc( p, sizeof( uint16 ) ); + end; + end; + + // skip slot numbers for now. + _pSlotNumbers := pUInt16(p); + inc( p, _NumSlots * sizeof( uint16 ) ); + + // Calculate the remainder of the tocentry length - that is the bytes used for TOC topic (title) text + titleLen := _pTOCEntry^.length - ( longword( p ) - longword( _pTOCEntry ) ); + + // Read title + if TitleLen > 0 then + SetTitleFromMem( p, TitleLen ) + else + Title := DefaultTitle; + + _ContentsLevel := ( Flags and TOCEntryLevelMask ); + _ShowInContents := Flags and TOCEntryHidden = 0; + if _ContentsLevel = 0 then + _ShowInContents := false; // hmmm.... + + _FontTable := FontTable; + _ReferencedFiles := ReferencedFiles; +end; + +destructor TTopic.Destroy; +begin + LogEvent(LogObjConstDest, 'TTopic.Destroy'); + DestroyListAndObjects( Links ); + DestroyListAndObjects( _Slots ); + inherited Destroy; +end; + +procedure TTopic.SetTitle( const NewValue: string ); +begin + _Title := NewValue; +end; + +procedure TTopic.SetTitleFromMem( const p: pointer; const Len: byte ); +begin + //FreePString( _Title ); + //GetMem( _Title, Len + 1 ); + //_Title^[ 0 ] := char( Len ); + //MemCopy( p, _Title + 1, Len ); + SetString(_Title, p, Len); +end; + +function TTopic.GetTitle: string; +begin + Result := _Title; +end; + +// Replace < and > characters with doubles << and >> +// for compatibility with richtextview. +// This works in place, assuming that instances of > or < are +// actually rare. In practice, IPF normally would insert these +// two characters as distinct words, but I don't want to assume that. +procedure SubstituteAngleBrackets( Var s: string ); +var + i: integer; +begin + i := 1; + while i <= Length( S ) do + begin + case S[ i ] of + '<': + begin + Insert( '<', s, i ); + inc( i ); + end; + + '>': + begin + Insert( '>', s, i ); + inc( i ); + end; + end; + inc( i ); + end; +end; + +function TTopic.GetImageText( CurrentAlignment: TIPFTextAlignment; + BitmapOffset: longint; + BitmapFlags: longint; + ImageOffsets: TList ): string; +var + BitmapIndex: longint; + OriginalAlignTag: string; + ImageTag: string; + AlignTag: string; +begin + BitmapIndex := ImageOffsets.IndexOf( pointer( BitmapOffset ) ); + if BitmapIndex = -1 then + BitmapIndex := ImageOffsets.Add( pointer( BitmapOffset ) ); + + ImageTag := '<image ' + + IntToStr( BitmapIndex ) + + '>'; + + if ( BitmapFlags and $08 ) > 0 then + begin + // stretch to fit - not implemented + end; + + // aligned + case CurrentAlignment of + itaLeft: + OriginalAlignTag := '<align left>'; + itaRight: + OriginalAlignTag := '<align right>'; + itaCenter, + itaCenterOnePara: + OriginalAlignTag := '<align center>'; + end; + + case BitmapFlags and 7 of + 0, // curious - should not occur? does in dbexpert.hlp + 1: // left + AlignTag := '<align left>'; + 2: // right + AlignTag := '<align right>'; + 4,5: // centre (4 is official, 5 seems to occur too) + AlignTag := '<align center>'; + end; + + Result := AlignTag + + ImageTag + + OriginalAlignTag; + + if ( BitmapFlags and $10 ) = 0 then + begin + // NOT runin, new lines before and after + Result := RTF_NewLine + Result + RTF_NewLine; + end; + +end; + +Procedure SaveImageText( BitmapOffset: longint; + BitmapFlags: longint; + Var F: TextFile; + ImageOffsets: TList ); +var + ImageIndex: longint; +begin + ImageIndex := ImageOffsets.IndexOf( pointer( BitmapOffset ) ); + if ImageIndex = -1 then + ImageIndex := ImageOffsets.Add( pointer( BitmapOffset ) ); + + Write( F, ':artwork name=' ); + Write( F, StrInSingleQuotes('img' + IntToStr(ImageIndex) + '.bmp') ); + + case BitmapFlags and 7 of + 2: // right + Write( F, ' align=right' ); + 4,5: // centre (4 is official, 5 seems to occur too) + Write( F, ' align=center' ); + end; + + if ( BitmapFlags and $10 ) > 0 then + begin + // runin + Write( F, ' runin' ); + end; + + // fit ... + Write( F, '.' ); +end; + +Procedure TTopic.ProcessLinkedImage( Var State: TParseState; + Var pData: pByte; + Var OutputString: string; + ImageOffsets: TList ); +var + EscapeLen: uint8; + EscapeCode: uint8; + SubEscapeCode: uint8; + BitmapOffset: longword; + BitmapFlags: uint8; + Link: THelpLink;//TInternalHelpLink; + LinkTopicIndex: integer; +begin + LinkTopicIndex := -1; + while true do + begin + EscapeLen := pData^; + SubEscapeCode := ( pData + 2 )^; + case SubEscapeCode of + HPART_DEFINE: + begin + BitmapFlags := ( pData + 3 )^; + BitmapOffset := pUInt32( pData + 4 )^; + end; + + HPART_HDREF: // define whole bitmap topic link? + begin + LinkTopicIndex := pUInt16( pData + 3 )^; + end; + end; + inc( pData, EscapeLen ); + + // Now pData points at next code or item + if pData^ <> IPF_ESC then + // not an escape code, done + break; + EscapeCode := (pData + 2) ^; + if EscapeCode <> ecLinkedImage then + // not a hyperlink code, done + break; + // another linked image code is coming up. + SubEscapeCode := ( pData + 3 )^; + if SubEscapeCode = HPART_DEFINE then + // started another linked image. + break; + inc( pData ); // move pointer to escape code len. + end; + + OutputString := GetImageText( State.Alignment, + BitmapOffset, + BitmapFlags, + ImageOffsets ); + + // Don't make it a link if we didn't find a + // overall link code, i.e. degrade gracefully. + if LinkTopicIndex > -1 then + begin + if CreateLink( State.LinkIndex, Link, TInternalHelpLink ) then + begin + TInternalHelpLink(Link).TopicIndex := LinkTopicIndex; + end; + + OutputString := GetBeginLink( State.LinkIndex ) + + OutputString + + GetEndLinkTags( State ); + + inc( State.LinkIndex ); + end; + +end; + +Procedure SaveLinkedImage( Var pData: pByte; + Var F: TextFile; + ImageOffsets: TList ); +var + EscapeLen: uint8; + EscapeCode: uint8; + SubEscapeCode: uint8; + BitmapOffset: longword; + BitmapFlags: uint8; + LinkTopicIndex: integer; +begin + LinkTopicIndex := -1; + while true do + begin + EscapeLen := pData^; + SubEscapeCode := ( pData + 2 )^; + case SubEscapeCode of + HPART_DEFINE: + begin + BitmapFlags := ( pData + 3 )^; + BitmapOffset := pUInt32( pData + 4 )^; + end; + + HPART_HDREF: // define whole bitmap topic link? + begin + LinkTopicIndex := pUInt16( pData + 3 )^; + end; + end; + inc( pData, EscapeLen ); + + // Now pData points at next code or item + if pData^ <> IPF_ESC then + // not an escape code, done + break; + EscapeCode := (pData + 2) ^; + if EscapeCode <> ecLinkedImage then + // not a hyperlink code, done + break; + // another linked image code is coming up. + SubEscapeCode := ( pData + 3 )^; + if SubEscapeCode = HPART_DEFINE then + // started another linked image. + break; + inc( pData ); // move pointer to escape code len. + end; + + SaveImageText( BitmapOffset, + BitmapFlags, + F, + ImageOffsets ); + + // Don't make it a link if we didn't find a + // overall link code, i.e. degrade gracefully. + if LinkTopicIndex > -1 then + begin + WriteLn( F, '' ); + WriteLn( F, ':artlink.' ); + Write( F, ':link reftype=hd' ); + Write( F, ' refid=' + IntToStr( LinkTopicIndex ) ); + WriteLn( F, '.' ); + WriteLn( F, ':eartlink.' ); + end; + +end; + +Procedure GetExtraLinkData( Link: TWindowedHelpLink; + pData: pUInt8 ); +var + LinkFlags1: uint8; + LinkFlags2: uint8; + LinkDataIndex: longint; + pLinkXY: pHelpXYPair; + pLinkData: pUInt8; +begin + LinkFlags1 := ( pData + 0 ) ^; + LinkFlags2 := ( pData + 1 ) ^; + + pLinkData := pData + 2; + + if ( LinkFlags1 and 1 ) > 0 then + begin + // position specified + pLinkXY := pHelpXYPair( pLinkData ); + ReadHelpPosition( pLinkXY^, Link.Rect ); + inc( pLinkData, sizeof( THelpXYPair ) ); + end; + + if ( LinkFlags1 and 2 ) > 0 then + begin + // size specified + pLinkXY := pHelpXYPair( pLinkData ); + ReadHelpSize( pLinkXY^, Link.Rect ); + inc( pLinkData, sizeof( THelpXYPair ) ); + end; + + if ( LinkFlags1 and 8 ) > 0 then + begin + // window controls specified - skip + inc( pLinkData, 2 ); + end; + + if ( LinkFlags2 and 4 ) > 0 then + begin + // group specified + Link.GroupIndex := pUInt16( pLinkData )^; + inc( LinkDataIndex, sizeof( uint16 ) ); + end; + + if ( LinkFlags1 and 64 ) > 0 then + begin + Link.Automatic := true; + end; + + if ( LinkFlags1 and 4 ) > 0 then + Link.ViewPort := true; + + if ( LinkFlags2 and 2 ) > 0 then + Link.Dependent := true; + + if ( LinkFlags1 and 128 ) > 0 then + Link.Split := true; + + // cant be bothered with the others. +end; + +// If the given link has already been decoded +// ie. the topic has been displayed before, +// then return the already decoded link & return false +// Otherwise, create a new link object & return true +function TTopic.CreateLink( Var LinkIndex: longint; + Var Link: THelpLink; + LinkClass: THelpLinkClass ): boolean; +begin + if LinkIndex >= Links.Count then + begin + Link := LinkClass.Create; + Link.HelpFile := HelpFile; + Links.Add( Link ); + Result := true; + end + else + begin + Link := THelpLink(Links[ LinkIndex ]); + Result := false; + end; +end; + +const + // size of the original View's default font + AverageViewCharWidth = 8; + +procedure GetMarginTag( const Margin: longint; + FontState: TFontState; + Var MarginString: string; + BreakIfPast: boolean ); +begin + MarginString := '<leftmargin '; + if FontState <> fsCustom then + // for standard fonts, scale margins to match font + MarginString := MarginString + IntToStr( Margin ) + else + // for custom fonts, since the IPF margins were always in + // terms of the standard font size, set the margin to a width based on that. + MarginString := MarginString + IntToStr( Margin * AverageViewCharWidth ) + ' pixels'; + + if BreakIfPast then + MarginString := MarginString + ' breakifpast'; + + MarginString := MarginString + '>'; +end; + +// TODO +function FullDoubleQuote( const s: string ): string; +begin + Result := StrDoubleQuote + + StrEscapeAllCharsBy(s, [], CharDoubleQuote) + + StrDoubleQuote; +end; + +// End URL, if it has been started. Go back and insert the start tag, +// and add the end tag. +procedure CheckForAutoURL( var Text: string; var State: TParseState ); +var + T: string; +begin + if State.StartOfTextBlock = -1 then + // haven't got any text yet + exit; + + TrimPunctuation( State.TextBlock ); + + if not CheckAndEncodeURL( State.TextBlock ) then + begin + // not a URL we know + State.TextBlock := ''; + exit; + end; + + // It's a URL. Insert link at start of URL + T := '<blue><link ' + PARAM_LINK_URL + ' "'; + T := T + State.TextBlock; + T := T + '">'; + Insert(T, Text, State.StartOfTextBlock); + Text := Text + GetEndLinkTags(State); + + State.TextBlock := ''; + State.StartOfTextBlock := -1; +end; + +procedure TTopic.TranslateIPFEscapeCode( Var State: TParseState; + Var pData: pUInt8; + var AText: String; + Var WordsOnLine: longint; + ImageOffsets: TList ); +var + EscapeLen: uint8; + EscapeCode: uint8; + + Link: THelpLink; //TInternalHelpLink; + FootnoteLink: THelpLink; //TFootnoteHelpLink; + LinkByResourceID: THelpLink; //THelpLinkByResourceID; + + Margin: integer; + + BitmapOffset: longword; + BitmapFlags: uint8; + + ColorCode: uint8; + StyleCode: uint8; + + FontIndex: uint8; + pFontSpec: pTHelpFontSpec; + + FaceName: string; + PointSize: longint; + QuotedFaceName: string; + + ExternalLinkFileIndex: uint8; + ExternalLinkTopicID: string; + + ProgramLink: string; + ProgramPath: string; + ProgramFilename: string; + ProgramInfo : TSerializableStringList; + tmpProgramLinkParts : TStrings; + + OutputString: string; +begin + EscapeLen := pData^; + EscapeCode := (pData + 1)^; + OutputString := ''; + + case EscapeCode of + + ecSetLeftMargin: + begin + CheckForAutoURL( AText, State ); + Margin := integer( ( pData + 2 )^ ); + GetMarginTag( Margin, State.FontState, OutputString, false ); + end; + + ecSetLeftMarginNewLine: + begin + CheckForAutoURL( AText, State ); + Margin := integer( ( pData + 2 )^ ); + GetMarginTag( Margin, State.FontState, OutputString, false ); + OutputString := OutputString + + RTF_NewLine; + end; + + ecSetLeftMarginFit: + begin + CheckForAutoURL( AText, State ); + Margin := integer( ( pData + 2 )^ ); + GetMarginTag( Margin, State.FontState, OutputString, true ); + // note that this will cause following tex to be "tabbed" across to the + // new margin position, if not yet there. + // if we are already past this margin then a new line should be started. + + end; + + ecSetLeftMarginHere: + begin + OutputString := '<leftmargin here>'; + end; + + ecHighlight1: + begin + StyleCode := ( pData + 2 )^; + if StyleCode <= High( IPFHighlight1Tags ) then + OutputString := IPFHighlight1Tags[ StyleCode ]; + if StyleCode = 0 then + State.ForegroundColorTag := '</color>'; + end; + + ecHighlight2: + begin + StyleCode := ( pData + 2 )^; + if StyleCode <= High( IPFHighlight2Tags ) then + OutputString := IPFHighlight2Tags[ StyleCode ]; + + if StyleCode = 0 then + State.ForegroundColorTag := '</color>' + else + State.ForegroundColorTag := OutputString; // only colours + end; + + ecLinkStart: + begin + CheckForAutoURL( AText, State ); + if CreateLink( State.LinkIndex, Link, TInternalHelpLink ) then + begin + TInternalHelpLink(Link).TopicIndex := pUInt16( pData + 2 )^; + + if EscapeLen >= 6 then + begin + GetExtraLinkData( TInternalHelpLink(Link), pData + 4 ); + end; + end; + + // If it's not an automatic link + // then put code in to show it. + if not TInternalHelpLink(Link).Automatic then + begin + OutputString := '<blue>' + + GetBeginLink( State.LinkIndex ); + end; + + inc( State.LinkIndex ); + end; + + ecFootnoteLinkStart: + begin + CheckForAutoURL( AText, State ); + if CreateLink( State.LinkIndex, FootnoteLink, TFootnoteHelpLink ) then + begin + TFootnoteHelpLink(FootnoteLink).TopicIndex := pUInt16( pData + 2 )^; + State.FootnoteLink := TFootnoteHelpLink(FootnoteLink); + end; + + OutputString := '<blue>' + GetBeginLink( State.LinkIndex ); + + inc( State.LinkIndex ); + end; + + ecStartLinkByResourceID: + begin + CheckForAutoURL( AText, State ); + if CreateLink( State.LinkIndex, LinkByResourceID, THelpLinkByResourceID ) then + begin + THelpLinkByResourceID(LinkByResourceID).ResourceID := pUInt16( pData + 2 )^; + + if EscapeLen >= 6 then + begin + GetExtraLinkData( THelpLinkByResourceID(LinkByResourceID), pData + 4 ); + end; + end; + + OutputString := '<blue>' + GetBeginLink( State.LinkIndex ); + + inc( State.LinkIndex ); + end; + + ecExternalLink: + begin + CheckForAutoURL( AText, State ); + // :link reftype=hd refid=... database=<filename> + ExternalLinkFileIndex := ( pData + 2 )^; + ExternalLinkTopicID := StrNPas( pchar(pData + 4), (pData + 3)^ ); + OutputString := '<blue><link ' + PARAM_LINK_EXTERNAL + ' ' + + IntToStr( ExternalLinkFileIndex ) + + ' ' + + ExternalLinkTopicID + + '>' + + end; + + ecProgramLink: + begin + CheckForAutoURL( AText, State ); + ProgramLink := StrNPas( pchar(pData + 3), EscapeLen-3 ); + + tmpProgramLinkParts := TStringList.Create; + StrExtractStrings(tmpProgramLinkParts, ProgramLink, [' '], #0); + ProgramPath := tmpProgramLinkParts[0]; + tmpProgramLinkParts.Destroy; + + ProgramFilename := ExtractFilename( ProgramPath ); + + if StrStartsWithIgnoringCase(ProgramFilename, PRGM_EXPLORER) + or StrStartsWithIgnoringCase(ProgramFilename, PRGM_NETSCAPE) + or StrStartsWithIgnoringCase(ProgramFilename, PRGM_MOZILLA) + or StrStartsWithIgnoringCase(ProgramFilename, PRGM_FIREFOX) + then + begin + OutputString := '<blue><link ' + PARAM_LINK_URL + ' ' + + FullDoubleQuote( ProgramLink ) + + '>'; + end + else + begin + ProgramInfo := TSerializableStringList.create; + ProgramInfo.add(ProgramPath); + ProgramInfo.add(ProgramLink); + OutputString := '<blue><link ' + PARAM_LINK_PROGRAM + ' ' + + ProgramInfo.getSerializedString + + '>'; + ProgramInfo.destroy; + end; + end; + + ecLinkEnd: + begin + OutputString := GetEndLinkTags( State ); + if State.FootnoteLink <> nil then + State.FootnoteLink := nil; + end; + + ecStartCharGraphics: + begin + State.FontState := fsFixed; + State.InCharGraphics := true; + OutputString := RTF_NewLine + RTF_NewLine + '<tt><wrap no>'; + State.Spacing := false; + WordsOnLine := 0; + end; + + ecEndCharGraphics: + begin + State.FontState := fsNormal; + State.InCharGraphics := false; + OutputString := '</tt><wrap yes>' + RTF_NewLine; + State.Spacing := true; + end; + + ecImage: + begin + CheckForAutoURL( AText, State ); + BitmapFlags := ( pData + 2 )^; + BitmapOffset := pUInt32( pData + 3 )^; + + OutputString := GetImageText( State.Alignment, + BitmapOffset, + BitmapFlags, + ImageOffsets ); + + if State.Spacing + AND (OutputString[Length(OutputString)] <> RTF_NewLine) // no space after a line break + then + OutputString := OutputString + ' '; + end; + + ecLinkedImage: + begin + CheckForAutoURL( AText, State ); + ProcessLinkedImage( State, + pData, + OutputString, + ImageOffsets ); + if State.Spacing then + OutputString := OutputString + ' '; + + // Note! Early exit, since the procedure + // will update pData. + AText := AText + OutputString; + exit; + end; + + ecStartLines: + begin + CheckForAutoURL( AText, State ); + // aligned text + case ( pData + 2 )^ of + 0, // just in case - to match image alignment oddities + 1: + begin + OutputString := RTF_NewLine + '<align left>'; + State.Alignment := itaLeft; + end; + + 2: + begin + OutputString := RTF_NewLine + '<align right>'; + State.Alignment := itaRight; + end; + + 4: + begin + OutputString := RTF_NewLine + '<align center>'; + State.Alignment := itaCenter; + end; + end; + OutputString := OutputString + '<wrap no>'; + WordsOnLine := 0; + end; + + ecEndLines: + begin + CheckForAutoURL( AText, State ); + // supposed to turn word wrap on, default font + OutputString := '<align left><wrap yes>'; // I guess... + State.Alignment := itaLeft; + end; + + ecForegroundColor: + begin + ColorCode := ( pData + 2 )^; + if ColorCode = 0 then + State.ForegroundColorTag := '</color>' + else if ColorCode <= High( IPFColors ) then + State.ForegroundColorTag := '<color ' + IPFColors[ ColorCode ] + '>'; + OutputString := State.ForegroundColorTag; + end; + + ecBackgroundColor: + begin + ColorCode := ( pData + 2 )^; + if ColorCode = 0 then + State.BackgroundColorTag := '</backcolor>' + else if ColorCode <= High( IPFColors ) then + State.BackgroundColorTag := '<backcolor ' + IPFColors[ ColorCode ] + '>'; + OutputString := State.BackgroundColorTag; + end; + + ecFontChange: + begin + FontIndex := ( pData + 2 )^; + if FontIndex = 0 then + begin + // back to default font + OutputString := '</font>'; + State.FontState := fsNormal; + end + else if FontIndex < _FontTable.Count then + begin + // valid font index + pFontSpec := _FontTable[ FontIndex ]; + +// if pFontSpec = SubstituteFixedFont then + if pFontSpec^.Codepage = High(word) then // Substitute Fixed Font detected + begin + OutputString := '<tt>'; + State.FontState := fsFixed; + end + else + begin +// pFontSpec := _FontTable[ FontIndex ]; + FaceName := StrNPas( pFontSpec^.FaceName, sizeof(pFontSpec^.FaceName) ); + // arbitrarily and capriciously use specified height * 2/3 + // as the point size - seems to correspond to what original + // view wanted... note this doesn't necessarily scale + // correctly, since default font could be different. whatever. + PointSize := (pFontSpec^.Height * 2) div 3; + + if PointSize < 8 then + PointSize := 8; + // quote font name, escape double quotes with duplicates + // e.g. Bob's "Big" Font would become + // "Bob's ""Big"" Font" + QuotedFaceName := FullDoubleQuote( FaceName ); + OutputString := '<font ' + + QuotedFaceName + + ' ' + + IntToStr( PointSize ) + + '>'; + { + // for when (if ever) RTV allows setting font + // by precise dimensions + + '[' + + IntToStr( pFontSpec ^. Width ) + + 'x' + + IntToStr( pFontSpec ^. Height ) + + ']'; + } + State.FontState := fsCustom; + end; + end; + end + end; // case escape code of... + + AText := AText + OutputString; + inc( pData, EscapeLen ); +end; + +// returns true if the escape code results in whitespace +// also updates the bits of State that relate to spacing +// ie. .Spacing, and .InCharGraphics (which affects whether +// spacing is reset at paragraph ends etc) +function TTopic.IPFEscapeCodeSpace( Var State: TParseState; + Var pData: pUInt8 ): boolean; +var + EscapeLen: uint8; + EscapeCode: uint8; + +begin + EscapeLen := pData^; + EscapeCode := (pData + 1) ^; + + result := false; // for most + case EscapeCode of + ecSetLeftMargin, + ecSetLeftMarginNewLine, + ecSetLeftMarginFit: + result := true; + + ecStartCharGraphics: + begin + result := true; + State.InCharGraphics := true; + State.Spacing := false; + end; + + ecEndCharGraphics: + begin + result := true; + State.InCharGraphics := false; + State.Spacing := true; + end; + + ecImage: + result := State.Spacing; + + ecLinkedImage: + result := State.Spacing; + + ecStartLines: + begin + result := true; + State.Spacing := false; + end; + + ecEndLines: + begin + result := true; + // supposed to turn word wrap on, default font + State.Spacing := true; + end; + end; // case escape code of... + + inc( pData, EscapeLen ); +end; + +procedure TTopic.EnsureSlotsLoaded; +var + i: longint; + pSlotNumber: puint16; + SlotNumber: uint16; + SlotHeader: TSlotHeader; + Slot: THelpTopicSlot; + bytes: integer; + expected: integer; +begin + if _Slots = nil then + begin + try + _Slots := TList.Create; + + // Read slot data + pSlotNumber := _pSlotNumbers; + + for i := 1 to _NumSlots do + begin + SlotNumber := pSlotNumber^; + + // Seek to start of slot + try + _FileHandle.Seek(_pSlotOffsets^[SlotNumber], soBeginning); + except + // not a valid offset + raise EHelpFileException.Create( ErrorCorruptHelpFile ); + end; + + // Read header + bytes := _FileHandle.Read(SlotHeader, SizeOf(TSlotHeader)); + if bytes <> SizeOf(TSlotHeader) then + // couldn't read slot header + raise EHelpFileException.Create( 'Failed to load Topic Slots.' ); + + // Create slot object + Slot := THelpTopicSlot.Create; + + Slot.LocalDictSize := SlotHeader.nLocalDict; + Slot.Size := SlotHeader.ntext; + + // Allocate and read slot dictionary + _FileHandle.Seek(SlotHeader.localDictPos, soBeginning); + expected := uint32(Slot.LocalDictSize) * sizeof(uint16); // size we need + if Slot.pLocalDictionary = nil then + // allocate memory + Slot.pLocalDictionary := GetMem(expected); + bytes := _FileHandle.Read(Slot.pLocalDictionary^, expected); + if bytes <> expected then + raise EHelpFileException.Create('Failed to read complete slot dictionary'); + + // Allocate and read slot data (text) + _FileHandle.Seek(_pSlotOffsets^[SlotNumber] + sizeof(TSlotHeader), soBeginning); + expected := Slot.Size; // size we need + if Slot.pData = nil then + // allocate memory + Slot.pData := GetMem(expected); + bytes := _FileHandle.Read(Slot.pData^, expected); + if bytes <> expected then + raise EHelpFileException.Create('Failed to read complete slot data (text)'); + + _Slots.Add( Slot ); + inc( pByte(pSlotNumber), sizeof( UInt16 ) ); + end; + except + on E: EHelpFileException do + begin + DestroyListAndObjects( _Slots ); + raise; + end; + end; + end; +end; + +// returns a global dict index. +// or, -1 for a whitespace item. +// or, -2 for end of text. +function TTopic.GetNextIPFTextItem( Var SlotIndex: longint; + Var pData: pUInt8; + Var State: TParseState ): longint; +var + Slot: THelpTopicSlot; + pSlotEnd: pUInt8; + + LocalDictIndex: uint8; +begin + while SlotIndex < _NumSlots do + begin + Slot := THelpTopicSlot(_Slots[ SlotIndex ]); + pSlotEnd := Slot.pData + Slot.Size; + + while pData < pSlotEnd do + begin + LocalDictIndex := pData^; + inc( pData ); + + if LocalDictIndex < Slot.LocalDictSize then + begin + // Normal word lookup + result := Slot.pLocalDictionary^[ LocalDictIndex ]; + exit; + end; + + // special code + case LocalDictIndex of + IPF_END_PARA: + begin + result := -1; + if not State.InCharGraphics then + State.Spacing := true; + exit; + end; + + IPF_CENTER: + begin + result := -1; + exit; + end; + + IPF_INVERT_SPACING: + begin + State.Spacing := not State.Spacing; + end; + + IPF_LINEBREAK: + begin + result := -1; + if not State.InCharGraphics then + State.Spacing := true; + exit; + end; + + IPF_SPACE: + begin + result := -1; + exit; + end; + + IPF_ESC: + begin + // escape sequence + if IPFEscapeCodeSpace( State, pData ) then + result := -1; + end; + end; + end; // while in slot... + inc( SlotIndex ); + end; + Result := -2; +end; + +// Checks to see if the given word (at pData) +// starts one of the given sequences, by looking forward +// If found, returns the length of the sequence. +function TTopic.CheckForSequence( WordSequences: TList; + SlotIndex: longint; + pData: pUint8; + State: TParseState; + GlobalDictIndex: longint + ): longint; +var + WordSequence: TList; + SequenceStepIndex: longint; + pSequenceStepWords: Uint32ArrayPointer; + + SequenceIndex: longint; + + SlotIndexTemp: longint; + pDataTemp: pUint8; + StateTemp: TParseState; +// s : string; + DictIndex: longint; +begin + result := 0; // if we don't find a match. + + for SequenceIndex := 0 to WordSequences.Count - 1 do + begin + WordSequence := TList(WordSequences[ SequenceIndex ]); + pSequenceStepWords := WordSequence[ 0 ]; + + if pSequenceStepWords^[ GlobalDictIndex ] > 0 then + begin + // matched first step in this sequence. Look ahead... + + SequenceStepIndex := 0; + + pDataTemp := pData; + SlotIndexTemp := SlotIndex; + StateTemp := State; + while true do + begin + inc( SequenceStepIndex ); + if SequenceStepIndex = WordSequence.Count then + begin + // have a match for the sequence, insert start highlight + Result := WordSequence.Count; + break; + end; + + // get words for next step in sequence + pSequenceStepWords := WordSequence[ SequenceStepIndex ]; + + DictIndex := GetNextIPFTextItem( SlotIndexTemp, + pDataTemp, + StateTemp ); + if DictIndex = -2 then + begin + // end of text - abort + break; + end; + + if DictIndex = -1 then + begin + // whitespace - abort + // for multi-word phrase searching - count this and subsequent whitespace... + break; + end; + +// s := _GlobalDictionary[ DictIndex ]; // for debug only + if not StrIsEmptyOrSpaces(_GlobalDictionary[ DictIndex ]) then + begin + if pSequenceStepWords^[ DictIndex ] = 0 then + begin + // word doesn't match - abort + break; + end; + end; + + end; // while + + end; + // else - doesn't match first step, do nothing + end; // for sequenceindex ... +end; + +// Main translation function. Turns the IPF data into +// a text string. Translates formatting codes into tags +// as for Rich Text Viewer. +procedure TTopic.GetText( HighlightSequences: TList; + // each element is a TList + // containing a sequence of possible words + // each element of each sequence + // is an array of flags for the dictionary + // indicating if the word is a allowed match at that step + // a match is any sequence that matches one or more words at each step. + ShowCodes: boolean; + ShowWordSeparators: boolean; + var Text: String; + ImageOffsets: TList; + HighlightMatches: TList ); +var + SlotIndex: integer; + Slot: THelpTopicSlot; + pData: pUInt8; + pSlotEnd: pUInt8; + + GlobalDictIndex: uint32; + + WordsOnLine: longint; + + StringToAdd: string; + LocalDictIndex: uint8; + + State: TParseState; + + EscapeLen: uint8; + i: longint; + + SequenceStepIndex: longint; +begin + if Links = nil then + Links := TList.Create; + + if HighlightMatches <> nil then + HighlightMatches.Clear; + + // Text.Clear; + ImageOffsets.Clear; + + try + EnsureSlotsLoaded; + except + on E: EHelpFileException do + begin + Text := Text + E.Message; + exit; + end; + end; + + WordsOnLine := 0; + + State.LinkIndex := 0; + State.FontState := fsNormal; // ? Not sure... this could be reset at start of slot + State.InCharGraphics := false; + State.Spacing := true; + State.ForegroundColorTag := '</color>'; + State.BackgroundColorTag := '</backcolor>'; + State.StartOfTextBlock := -1; + State.TextBlock := ''; + State.FootnoteLink := nil; + Text := Text + '<leftmargin 1>'; + + SequenceStepIndex := 0; + + for SlotIndex := 0 to _NumSlots - 1 do + begin + if not State.InCharGraphics then + State.Spacing := true; // this is just a guess as to the exact view behaviour. + // inf.txt indicates that spacing is reset to true at + // slot (cell) start, but that doesn't seem to be the + // case when in character graphics... hey ho. + + Slot := THelpTopicSlot(_Slots[ SlotIndex ]); + pData := Slot.pData; + pSlotEnd := pData + Slot.Size; + State.Alignment := itaLeft; + + while pData < pSlotEnd do + begin + LocalDictIndex := pData^; + inc( pData ); + + if LocalDictIndex < Slot.LocalDictSize then + begin + // Normal word lookup + GlobalDictIndex := Slot.pLocalDictionary^[ LocalDictIndex ]; + + if ShowWordSeparators then + Text := Text + '{' + IntToStr( GlobalDictIndex )+ '}'; + + // normal lookup + if GlobalDictIndex < _GlobalDictionary.Count then + StringToAdd := _GlobalDictionary[ GlobalDictIndex ] + else + StringToAdd := ''; + + if StrIsEmptyOrSpaces( StringToAdd ) then + begin + // spaces only... + CheckForAutoURL( Text, State ); + end + else + begin + // really is a word, not a space. + + // store string into "word" + if Length(State.TextBlock) = 0 then + // store start of block + State.StartOfTextBlock := Length(Text); + + State.TextBlock := State.TextBlock + StringToAdd; + + SubstituteAngleBrackets( StringToAdd ); + + if HighlightSequences <> nil then + begin + if SequenceStepIndex > 0 then + begin + // currently highlighting a sequence. + dec( SequenceStepIndex ); + if SequenceStepIndex = 0 then + begin + // now finished, insert end highlight + StringToAdd := StringToAdd + + State.BackgroundColorTag; + + end; + end + else + begin + // not yet in a sequence, searching. + SequenceStepIndex := + CheckForSequence( HighlightSequences, + SlotIndex, + pData, + State, + GlobalDictIndex ); + + if SequenceStepIndex > 0 then + begin + // this word starts a sequence! + if HighlightMatches <> nil then + HighlightMatches.Add( pointer( Length(Text) ) ); + StringToAdd := '<backcolor #' + + IntToHex( Settings.Colors[ SearchHighlightTextColorIndex ], 6 ) + + '>' + + StringToAdd; + dec( SequenceStepIndex ); + if SequenceStepIndex = 0 then + // and ends it. + StringToAdd := StringToAdd + + State.BackgroundColorTag; + end; + + end; + end; // if processing sequence + inc( WordsOnLine ); + end; + + Text := Text + StringToAdd; + + if State.FootnoteLink <> nil then + begin + State.FootnoteLink.Title := State.FootnoteLink.Title + StringToAdd; + if State.Spacing then + begin + State.FootnoteLink.Title := State.FootnoteLink.Title + ' '; + end; + end; + + if State.Spacing then + begin + CheckForAutoURL( Text, State ); + Text := Text + ' '; + end; + end + else + begin + // special code + + if ShowCodes then + begin + Text := Text + '[' + IntToHex( LocalDictIndex, 2 ); + if LocalDictIndex = IPF_ESC then + begin + EscapeLen := pData^; + for i := 1 to EscapeLen - 1 do + Text := Text + ' ' + IntToHex( ( pData + i )^, 2 ); + end; + Text := Text + ']'; + end; + + case LocalDictIndex of + IPF_END_PARA: + begin + if SlotIndex = 0 then + if pData - 1 = Slot.pData then + // ignore first FA, not needed with RichTextView + continue; + + CheckForAutoURL( Text, State ); + if State.Alignment = itaCenterOnePara then + begin + State.Alignment := itaLeft; + Text := Text + '<align left>'; + end; + Text := Text + RTF_NewLine; + + if WordsOnLine > 0 then + Text := Text + RTF_NewLine; + + if not State.InCharGraphics then + State.Spacing := true; + + WordsOnLine := 0; + end; + + IPF_CENTER: + begin + CheckForAutoURL( Text, State ); + Text := Text + RTF_NewLine + '<align center>'; + State.Alignment := itaCenterOnePara; + end; + + IPF_INVERT_SPACING: + begin + if not State.InCharGraphics then + State.Spacing := not State.Spacing; + end; + + IPF_LINEBREAK: + begin + CheckForAutoURL( Text, State ); + + if State.Alignment = itaCenterOnePara then + begin + State.Alignment := itaLeft; + Text := Text + '<align left>'; + end; + Text := Text + RTF_NewLine; + if not State.InCharGraphics then + State.Spacing := true; + WordsOnLine := 0; + end; + + IPF_SPACE: + begin + CheckForAutoURL( Text, State ); + if State.Spacing then + Text := Text + ' '; + end; + + IPF_ESC: + begin + // escape sequence + TranslateIPFEscapeCode( State, + pData, + Text, + WordsOnLine, + ImageOffsets ); + + end; + + end; // case code of... + end; + end; // for slotindex = ... + end; + State.TextBlock := ''; +end; + +function TTopic.SearchForWord( DictIndex: integer; + StopAtFirstOccurrence: boolean ) + : longint; +var + SlotIndex: integer; + Slot: THelpTopicSlot; + pData: pUInt8; + pSlotEnd: pUInt8; + + EscapeLen: longint; + + GlobalDictIndex: uint32; + + LocalDictIndex: uint8; +begin + EnsureSlotsLoaded; + + Result := 0; + for SlotIndex := 0 to _NumSlots - 1 do + begin + Slot := THelpTopicSlot(_Slots[ SlotIndex ]); + + pData := Slot.pData; + + pSlotEnd := pData + Slot.Size; + + while pData < pSlotEnd do + begin + LocalDictIndex := pData^; + + if LocalDictIndex < Slot.LocalDictSize then + begin + // Normal word lookup + GlobalDictIndex := Slot.pLocalDictionary^[ LocalDictIndex ]; + + if GlobalDictIndex = DictIndex then + begin + inc( result ); + if StopAtFirstOccurrence then + exit; + end; + end + else + begin + // special code + if LocalDictIndex = $ff then + begin + // escape string, skip it + EscapeLen := ( pData + 1 ) ^; + inc( pData, EscapeLen ); + end; + end; + + inc( pData ); + end; // for slotindex = ... + end; +end; + +// Search for a sequence of bytes, including in escape codes +// this is for debugging to allow finding specific sequences +function TTopic.SearchForData( Data: pbyte; + DataLen: integer ): boolean; +var + SlotIndex: integer; + Slot: THelpTopicSlot; + pData: pUInt8; + pSlotEnd: pUInt8; + + pHold: pUint8; + pSearch: pUint8; +begin + EnsureSlotsLoaded; + + for SlotIndex := 0 to _NumSlots - 1 do + begin + Slot := THelpTopicSlot(_Slots[ SlotIndex ]); + + pSearch := Data; + pHold := Slot.pData; + pData := Slot.pData; + pSlotEnd := Slot.pData + Slot.Size; + + while pHold < pSlotEnd do + begin + if pData^ = pSearch^ then + begin + // byte matches + inc( pData ); + inc( pSearch ); + if ( pSearch >= Data + DataLen ) then + begin + // matches + result := true; + exit; + end + end + else + begin + // no match + pSearch := Data; + inc( pHold ); + pData := pHold; + end; + end; // for slotindex = ... + end; + + result := false; // not found +end; + +function TTopic.SearchForWordSequences( WordSequence: TList; + StopAtFirstOccurrence: boolean ): longint; +var + SlotIndex: integer; + Slot: THelpTopicSlot; + pData: pUInt8; + pSlotEnd: pUInt8; + + EscapeLen: longint; + + GlobalDictIndex: uint32; + IsWord: boolean; + WordRelevance: uint32; + + CurrentMatchRelevance: uint32; // total relevances for words matched so far + // in the current sequence + +// CurrentMatch: string; // useful for debugging only + LocalDictIndex: uint8; + + SequenceIndex: longint; + SequenceStartSlotIndex: longint; + pSequenceStartData: pUInt8; + + pStepWordRelevances: UInt32ArrayPointer; // word relevances for the current step in the sequence + + // get the current slot start and end pointers + procedure GetSlot; + begin + Slot := THelpTopicSlot(_Slots[ SlotIndex ]); + pData := Slot.pData; + pSlotEnd := pData + Slot.Size; + end; + + // get pointer to the current set of word relevances + procedure GetStepFlags; + begin + pStepWordRelevances := WordSequence[ SequenceIndex ]; + end; + + // store the current point as start of a sequence + procedure StoreStartOfSequence; + begin + SequenceIndex := 0; + SequenceStartSlotIndex := SlotIndex; + pSequenceStartData := pData; + CurrentMatchRelevance := 0; +// CurrentMatch := ''; + GetStepFlags; + end; + +begin + Result := 0; + + EnsureSlotsLoaded; + + if _NumSlots = 0 then + // thar's nowt in yon topic, cannae be a match laid + exit; + + SlotIndex := 0; + + GetSlot; + + StoreStartOfSequence; + + while true do + begin + LocalDictIndex := pData^; + IsWord := false; + if LocalDictIndex < Slot.LocalDictSize then + begin + IsWord := true; + // Normal word lookup, so get the global dict idnex before we + // (potentially) move to next slot + GlobalDictIndex := Slot.pLocalDictionary^[ LocalDictIndex ]; + end; + + inc( pData ); + if pData >= pSlotEnd then + begin + // reached end of slot, next please + inc( SlotIndex ); + if SlotIndex < _NumSlots then + GetSlot; + // else - there is nothing more to search + // but we need to check this last item + end; + + if IsWord then + begin + // Normal word lookup + WordRelevance := 0; + + if GlobalDictIndex < _GlobalDictionary.Count then + if not StrIsEmptyOrSpaces( _GlobalDictionary[ GlobalDictIndex ] ) then; + WordRelevance := pStepWordRelevances^[ GlobalDictIndex ]; + + if WordRelevance > 0 then + begin + // Found a matching word + inc( CurrentMatchRelevance, WordRelevance ); +// debug: +// CurrentMatch := CurrentMatch + +// pstring( _GlobalDictionary[ GlobalDictIndex ] )^; + + if SequenceIndex = 0 then + begin + // remember next start point + SequenceStartSlotIndex := SlotIndex; + pSequenceStartData := pData; + end; + + inc( SequenceIndex ); + + if SequenceIndex < WordSequence.Count then + begin + // get next set of flags. + GetStepFlags; + end + else + begin + // found a complete sequence. Cool! + + inc( result, CurrentMatchRelevance ); + + if StopAtFirstOccurrence then + exit; + + // start looking from the beginning of the sequence again. + StoreStartOfSequence; + end; + end + else + begin + // not a match at this point, restart search + if SequenceIndex > 0 then + begin + // we had matched one or more steps already, + // back to start of sequence AND back to + // point we started matching from (+1) + SequenceIndex := 0; + CurrentMatchRelevance := 0; +// CurrentMatch := ''; + SlotIndex := SequenceStartSlotIndex; + GetSlot; + pData := pSequenceStartData; + GetStepFlags; + end + else + begin + // haven't matched anything yet. + // update start of sequence + SequenceStartSlotIndex := SlotIndex; + pSequenceStartData := pData; + end; + end; + end + else + begin + // special code + if LocalDictIndex = $ff then + begin + // escape string, skip it + EscapeLen := pData ^; + inc( pData, EscapeLen ); + end; + end; + + if SlotIndex >= _NumSlots then + begin + // finished searching topic + break; + end; + + // next item + end; +end; + + +function TTopic.CountWord( DictIndex: integer ): longint; +begin + Result := SearchForWord( DictIndex, false ); +end; + +function TTopic.ContainsWord( DictIndex: integer ): boolean; +begin + Result := SearchForWord( DictIndex, true ) > 0; +end; + +// Gets the window dimensions specified by this topic's +// contents header +procedure TTopic.GetContentsWindowRect( ContentsRect: THelpWindowRect ); +var + extendedinfo: TExtendedTOCEntry; + XY: THelpXYPair; + p: pbyte; + + Flags: byte; +begin + Flags := _pTOCEntry ^.flags; + p := pByte( _pTOCEntry + sizeof( TTOCEntryStart ) ); + + ContentsRect.Left := 0; + ContentsRect.Bottom := 0; + ContentsRect.Width := 100; + ContentsRect.Height := 100; + + if ( Flags and TOCEntryExtended ) > 0 then + begin + // have more details available... + ExtendedInfo.w1 := p^; + ExtendedInfo.w2 := ( p+1) ^; + inc( p, sizeof( ExtendedInfo ) ); + + if ( ExtendedInfo.w1 and 1 ) > 0 then + begin + // read origin + XY := pHelpXYPair( p )^; + inc( p, sizeof( XY ) ); + ReadHelpPosition( XY, ContentsRect ); + end; + if ( ExtendedInfo.w1 and 2 ) > 0 then + begin + // read size + XY := pHelpXYPair( p )^; + inc( p, sizeof( XY ) ); + ReadHelpSize( XY, ContentsRect ); + end; + end; +end; + +const + IPFColorNames: array[ 0..15 ] of string = + ( + 'default', + 'blue', + 'red', + 'pink', + 'green', + 'cyan', + 'yellow', + 'neutral', +// 'brown', ?? + 'darkgray', + 'darkblue', + 'darkred', + 'darkpink', + 'darkgreen', + 'darkcyan', + 'black', + 'palegray' + ); + +Procedure SaveExtraLinkData( Link: TWindowedHelpLink; + pData: pUInt8 ); +var + LinkFlags1: uint8; + LinkFlags2: uint8; + LinkDataIndex: longint; + pLinkXY: pHelpXYPair; + pLinkData: pUInt8; +begin + LinkFlags1 := ( pData + 0 ) ^; + LinkFlags2 := ( pData + 1 ) ^; + + pLinkData := pData + 2; + + if ( LinkFlags1 and 1 ) > 0 then + begin + // position specified + pLinkXY := pHelpXYPair( pLinkData ); + inc( pLinkData, sizeof( THelpXYPair ) ); + end; + + if ( LinkFlags1 and 2 ) > 0 then + begin + // size specified + pLinkXY := pHelpXYPair( pLinkData ); + inc( pLinkData, sizeof( THelpXYPair ) ); + end; + + if ( LinkFlags1 and 8 ) > 0 then + begin + // window controls specified - skip + inc( pLinkData, 2 ); + end; + + if ( LinkFlags2 and 4 ) > 0 then + begin + // group specified + Link.GroupIndex := pUInt16( pLinkData )^; + inc( LinkDataIndex, sizeof( uint16 ) ); + end; + + if ( LinkFlags1 and 64 ) > 0 then + begin + Link.Automatic := true; + end; + + if ( LinkFlags1 and 4 ) > 0 then + Link.ViewPort := true; + + if ( LinkFlags2 and 2 ) > 0 then + Link.Dependent := true; + + if ( LinkFlags1 and 128 ) > 0 then + Link.Split := true; + + // cant be bothered with the others. +end; + +procedure TTopic.SaveIPFEscapeCode( Var State: TParseState; + Var pData: pUInt8; + Var F: TextFile; + ImageOffsets: TList ); +var + EscapeLen: uint8; + EscapeCode: uint8; + + Margin: integer; + + BitmapOffset: longword; + BitmapFlags: uint8; + + ColorCode: uint8; + StyleCode: uint8; + + FontIndex: uint8; + pFontSpec: pTHelpFontSpec; + + FaceName: string; + + ExternalLinkFileIndex: uint8; + ExternalLinkTopicID: string; + + ProgramLink: string; + ProgramPath: string; + tmpProgramLinkParts : TStrings; + + OutputString: string; +begin + EscapeLen := pData^; + EscapeCode := (pData + 1) ^; + OutputString := ''; + + case EscapeCode of + + ecSetLeftMargin: + begin + Margin := integer( ( pData + 2 )^ ); + GetMarginTag( Margin, State.FontState, OutputString, false ); + end; + + ecSetLeftMarginNewLine: + begin + Margin := integer( ( pData + 2 )^ ); + GetMarginTag( Margin, State.FontState, OutputString, false ); + OutputString := OutputString + + RTF_NewLine; + end; + + ecSetLeftMarginFit: + begin + Margin := integer( ( pData + 2 )^ ); + GetMarginTag( Margin, State.FontState, OutputString, true ); + // note that this will cause following tex to be "tabbed" across to the + // new margin position, if not yet there. + // if we are already past this margin then a new line should be started. + + end; + + ecSetLeftMarginHere: + begin + OutputString := '<leftmargin here>'; + end; + + ecHighlight1: // hp1,2,3, 5,6,7 + begin + StyleCode := ( pData + 2 ) ^; + if StyleCode > 3 then + StyleCode := StyleCode + 1; // 4, 8 and 9 are expressed in highlight2 code + + if StyleCode > 0 then + Write( F, ':hp' + IntToStr( StyleCode ) + '.' ) + else + Write( F, ':ehp' + IntToStr( State.StyleCode ) + '.' ); + State.StyleCode := StyleCode; + end; + + ecHighlight2: // hp4, 8, 9 + begin + StyleCode := ( pData + 2 ) ^; + case StyleCode of + 1: StyleCode := 4; + 2: StyleCode := 8; + 3: StyleCode := 9; + end; + + if StyleCode > 0 then + Write( F, ':hp' + IntToStr( StyleCode ) + '.' ) + else + Write( F, ':ehp' + IntToStr( State.StyleCode ) + '.' ); + State.StyleCode := StyleCode; + end; + + ecLinkStart: + begin + Write( F, ':link reftype=hd' ); // link to heading + + Write( F, ' refid=' + IntToStr( pUInt16( pData + 2 )^ ) ); + + { + if EscapeLen >= 6 then + begin + GetExtraLinkData( Link, pData + 4 ); + end;} + +// if Link.Automatic then +// Write( F, ' auto' ); + + Write( F, '.' ); + + inc( State.LinkIndex ); + end; + + ecFootnoteLinkStart: + begin + Write( F, ':link reftype=fn refid=fn' + + IntToStr( pUInt16( pData + 2 )^ ) + + '.' ); + inc( State.LinkIndex ); + end; + + ecStartLinkByResourceID: + begin + Write( F, ':link reftype=hd res=' + + IntToStr( pUInt16( pData + 2 )^ ) + + '.' ); + + inc( State.LinkIndex ); + end; + + ecExternalLink: + begin + ExternalLinkFileIndex := ( pData + 2 )^; + ExternalLinkTopicID := StrNPas( pchar( pData + 4 ), ( pData + 3 )^ ); + Write( F, ':link reftype=hd ' + + ' refid=' + StrInSingleQuotes( ExternalLinkTopicID ) + + ' database=' + StrInSingleQuotes( _ReferencedFiles[ ExternalLinkFileIndex ] ) + + '.' ); + + end; + + ecProgramLink: + begin + ProgramLink := StrNPas( pchar( pData + 3 ), EscapeLen - 3 ); + tmpProgramLinkParts := TStringList.Create; + StrExtractStrings(tmpProgramLinkParts, ProgramLink, [' '], #0); + ProgramPath := tmpProgramLinkParts[0]; + tmpProgramLinkParts.Destroy; + + Write( F, ':link reftype=launch' + + ' object=' + StrInSingleQuotes( ProgramPath ) + + ' data=' + StrInSingleQuotes( ProgramLink ) + + '.' ); + end; + + ecLinkEnd: + begin + Write( F, ':elink.' ); + if State.FootnoteLink <> nil then + State.FootnoteLink := nil; + end; + + ecStartCharGraphics: + begin + State.FontState := fsFixed; + State.InCharGraphics := true; + WriteLn( F, '' ); + WriteLn( F, ':cgraphic.' ); + State.Spacing := false; + end; + + ecEndCharGraphics: + begin + State.FontState := fsNormal; + State.InCharGraphics := false; + WriteLn( F, '' ); + WriteLn( F, ':ecgraphic.' ); + State.Spacing := true; + end; + + ecImage: + begin + BitmapFlags := ( pData + 2 )^; + BitmapOffset := pUInt32( pData + 3 )^; + + SaveImageText( BitmapOffset, BitmapFlags, F, ImageOffsets ); + + if State.Spacing then + Write( F, ' ' ); + end; + + ecLinkedImage: + begin + SaveLinkedImage( pData, F, ImageOffsets ); + // Note! Early exit, since the procedure + // will update pData. + exit; + end; + + ecStartLines: + begin + WriteLn( F, '' ); + // aligned text + case ( pData + 2 )^ of + 0, // just in case - to match image alignment oddities + 1: + begin + WriteLn( F, ':lines.' ); + State.Alignment := itaLeft; + end; + + 2: + begin + WriteLn( F, ':lines align=right.' ); + State.Alignment := itaRight; + end; + + 4: + begin + WriteLn( F, ':lines align=center.' ); + State.Alignment := itaCenter; + end; + end; + end; + + ecEndLines: + begin + // supposed to turn word wrap on, default font + WriteLn( F, '' ); + WriteLn( F, ':elines.' ); + State.Alignment := itaLeft; + end; + + ecForegroundColor: + begin + ColorCode := ( pData + 2 )^; + + if ColorCode < High( IPFColorNames ) then + Write( F, ':color fc=' + IPFColorNames[ ColorCode ] + '.' ); + end; + + ecBackgroundColor: + begin + ColorCode := ( pData + 2 )^; + if ColorCode < High( IPFColorNames ) then + Write( F, ':color bc=' + IPFColorNames[ ColorCode ] + '.' ); + end; + + ecFontChange: + begin + FontIndex := ( pData + 2 )^; + if FontIndex = 0 then + begin + // back to default font + Write( F, ':font facename=default.' ); + State.FontState := fsNormal; + end + else if FontIndex < _FontTable.Count then + begin + // valid font index + pFontSpec := _FontTable[ FontIndex ]; + + if pFontSpec = SubstituteFixedFont then + begin + // oops. + OutputString := '<tt>'; + State.FontState := fsFixed; + end + else + begin + pFontSpec := _FontTable[ FontIndex ]; + FaceName := StrNPas( pFontSpec^.FaceName, + sizeof( pFontSpec^.FaceName ) ); + Write( F, + ':font facename=' + StrInSingleQuotes( FaceName ) + + ' size=' + IntToStr( pFontSpec^.Height ) + + 'x' + IntToStr( pFontSpec^.Width ) + + '.' ); + State.FontState := fsCustom; + end; + end; + end + end; // case escape code of... + + // Write( F, OutputString ); + + inc( pData, EscapeLen ); +end; + +procedure TTopic.SaveToIPF( Var f: TextFile; + ImageOffsets: TList ); +var + SlotIndex: integer; + Slot: THelpTopicSlot; + pData: pUInt8; + pSlotEnd: pUInt8; + GlobalDictIndex: uint32; + StringToAdd: string; + LocalDictIndex: uint8; + State: TParseState; + SequenceStepIndex: longint; + LineLen: longint; + c: char; +begin + EnsureSlotsLoaded; + + State.LinkIndex := 0; + State.FontState := fsNormal; // ? Not sure... this could be reset at start of slot + State.InCharGraphics := false; + State.Spacing := true; + State.ForegroundColorTag := '</color>'; + State.BackgroundColorTag := '</backcolor>'; + + State.StartOfTextBlock := -1; + State.TextBlock := ''; + + State.FootnoteLink := nil; + + State.StyleCode := 0; + + SequenceStepIndex := 0; + + LineLen := 0; + + for SlotIndex := 0 to _NumSlots - 1 do + begin + if not State.InCharGraphics then + State.Spacing := true; // this is just a guess as to the exact view behaviour. + // inf.txt indicates that spacing is reset to true at + // slot (cell) start, but that doesn't seem to be the + // case when in character graphics... hey ho. + + Slot := THelpTopicSlot(_Slots[ SlotIndex ]); + + pData := Slot.pData; + + pSlotEnd := pData + Slot.Size; + + State.Alignment := itaLeft; + + while pData < pSlotEnd do + begin + LocalDictIndex := pData^; + inc( pData ); + + if LocalDictIndex < Slot.LocalDictSize then + begin + // Normal word lookup + GlobalDictIndex := Slot.pLocalDictionary^[ LocalDictIndex ]; + + // normal lookup + if GlobalDictIndex < _GlobalDictionary.Count then + StringToAdd := _GlobalDictionary[ GlobalDictIndex ] + else + StringToAdd := ''; + + if (Length( StringToAdd ) = 1) and Settings.IPFTopicSaveAsEscaped then + begin + // could be symbol + c := StringToAdd[ 1 ]; + case C of + '&': StringToAdd := '&.'; + '''': StringToAdd := '&apos.'; + '*': StringToAdd := '&asterisk.'; + '@': StringToAdd := '&atsign.'; + '\': StringToAdd := '&bsl.'; + '^': StringToAdd := '&caret.'; + '"': StringToAdd := '&osq.'; + ':': StringToAdd := '&colon.'; + '.': StringToAdd := '&per.'; + '(': StringToAdd := '&lpar.'; + ')': StringToAdd := '&rpar.'; + '/': StringToAdd := '&slash.'; + ',': StringToAdd := '&comma.'; + '-': StringToAdd := '&hyphen.'; + '_': StringToAdd := '&us.'; + '~': StringToAdd := '&tilde.'; + '+': StringToAdd := '&plus.'; + '>': StringToAdd := '>.'; + ';': StringToAdd := '&semi.'; + Chr($da): StringToAdd := '+'; + Chr($c4): StringToAdd := '-'; + Chr($b3): StringToAdd := '|'; + Chr($c3): StringToAdd := '|'; + Chr($bf): StringToAdd := '+'; + end; + end; + + inc( LineLen, Length( StringToAdd ) ); + if ( LineLen > 80 ) and ( not State.InCharGraphics ) then + begin + WriteLn( F ); + LineLen := 0; + end; + + Write( F, StringToAdd ); +{ + if State.FootnoteLink <> nil then + begin + State.FootnoteLink.Title := State.FootnoteLink.Title + StringToAdd; + if State.Spacing then + begin + State.FootnoteLink.Title := State.FootnoteLink.Title + ' '; + end; + end; + } + if State.Spacing then + begin + Write( F, ' ' ); + inc( LineLen ); + end; + end + else + begin + // special code + + case LocalDictIndex of + IPF_END_PARA: + begin + WriteLn( F, '' ); + Write( F, ':p.' ); + LineLen := 3; + + if not State.InCharGraphics then + State.Spacing := true; + end; + + IPF_CENTER: + begin + WriteLn( F, '' ); + Write( F, '.ce ' ); // remainder of this line is centered. + LineLen := 4; + State.Alignment := itaCenterOnePara; + end; + + IPF_INVERT_SPACING: + begin + State.Spacing := not State.Spacing; + end; + + IPF_LINEBREAK: + begin + WriteLn( F, '' ); + if not State.InCharGraphics then + WriteLn( F, '.br ' ); // break must be the only thing on the line + + LineLen := 0; + if not State.InCharGraphics then + State.Spacing := true; + end; + + IPF_SPACE: + begin + if State.Spacing then + Write( F, ' ' ) + else + Write( F, ' ' ); + end; + + IPF_ESC: + begin + // escape sequence + SaveIPFEscapeCode( State, + pData, + F, + ImageOffsets ); + end; + + end; // case code of... + end; + end; // for slotindex = ... + end; + State.TextBlock := ''; + +end; + +// Compares two topics for purposes of sorting by +// search match relevance +function TopicRelevanceCompare( Item1, Item2: pointer ): longint; +var + Topic1, Topic2: TTopic; +begin + Topic1 := TTopic(Item1); + Topic2 := TTopic(Item2); + + if Topic1.SearchRelevance > Topic2.SearchRelevance then + Result := -1 + else if Topic1.SearchRelevance < Topic2.SearchRelevance then + Result := 1 + else + Result := 0; +end; + +// Compares two topics for purposes of sorting by title +function TopicTitleCompare( Item1, Item2: pointer ): longint; +begin + Result := CompareText( TTopic( Item1 )._Title, + TTopic( Item2 )._Title ); +end; + + +end. diff --git a/docview/src/HelpWindowDimensions.pas b/docview/src/HelpWindowDimensions.pas new file mode 100644 index 00000000..c8335176 --- /dev/null +++ b/docview/src/HelpWindowDimensions.pas @@ -0,0 +1,126 @@ +unit HelpWindowDimensions; + +{$mode objfpc}{$H+} + + // NewView - a new OS/2 Help Viewer + // Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) + // This software is released under the Gnu Public License - see readme.txt + +interface + +uses + IPFFileFormatUnit; + +const + ptCharacters = 0; + ptPercentage = 1; + ptPixels = 2; + ptPoints = 3; + ptDynamic = 4; + + XPosRight = 577; // some random values as markers + YPosTop = 577; + XYPosCenter = 578; + +type + THelpWindowRect = class(TObject) + public + Left: longint; // xposright means, right aligned + Bottom: longint; // xpostop means top aligned + // both: xyposcenter means centered + Width: longint; + Height: longint; + constructor Create; + procedure Assign(Rect: THelpWindowRect); + end; + +var + FootnoteRect: THelpWindowRect; + +procedure ReadHelpSize(const XY: THelpXYPair; var Rect: THelpWindowRect); +procedure ReadHelpPosition(const XY: THelpXYPair; var Rect: THelpWindowRect); + + +implementation + +constructor THelpWindowRect.Create; +begin + Left := -1; + Bottom := -1; + Width := -1; + Height := -1; +end; + +procedure THelpWindowRect.Assign(Rect: THelpWindowRect); +begin + Left := Rect.Left; + Bottom := Rect.Bottom; + Width := Rect.Width; + Height := Rect.Height; +end; + +function GetPos(const PositionType: uint8; const Value: longint): longint; +begin + case PositionType of + ptCharacters: + Result := Value; + ptPercentage: + Result := Value; + ptPixels: + Result := Value * 5; + ptPoints: + Result := Value; + ptDynamic: + case Value of + 1: + Result := 0; // left + 2: + Result := XPosRight; // right + 4: + Result := YPosTop; // top + 8: + Result := 0; // bottom + 16: + Result := XYPosCenter; // center. + end; + end; +end; + +procedure ReadHelpPosition(const XY: THelpXYPair; var Rect: THelpWindowRect); +var + XPositionType: uint8; + YPositionType: uint8; +begin + // read origin + XPositionType := XY.Flags div 16; + YPositionType := XY.Flags and 15; + + if XY.X <> $ffff then + Rect.Left := GetPos(XPositionType, XY.X); + if XY.Y <> $ffff then + Rect.Bottom := GetPos(YPositionType, XY.Y); +end; + +procedure ReadHelpSize(const XY: THelpXYPair; var Rect: THelpWindowRect); +begin + if XY.X <> $ffff then + Rect.Width := XY.X; + if XY.Y <> $ffff then + Rect.Height := XY.Y; +end; + +initialization + FootnoteRect := THelpWindowRect.Create; + with FootnoteRect do + begin + Left := 10; + Width := 80; + Bottom := 10; + Height := 40; + end; + +finalization + FootnoteRect.Free; + +end. + diff --git a/docview/src/IPFEscapeCodes.pas b/docview/src/IPFEscapeCodes.pas new file mode 100644 index 00000000..41e743d4 --- /dev/null +++ b/docview/src/IPFEscapeCodes.pas @@ -0,0 +1,47 @@ +Unit IPFEscapeCodes; + +{$mode objfpc}{$H+} + + +// NewView - a new OS/2 Help Viewer +// Copyright 2001 Aaron Lawrence (aaronl at consultant dot com) +// This software is released under the Gnu Public License - see readme.txt + +Interface + +// List of IPF escape codes. Not complete! Many are just used +// as magic numbers in HelpTopic.pas + +const + // Basic byte codes + IPF_END_PARA = $fa; + IPF_CENTER = $fb; + IPF_INVERT_SPACING = $fc; + IPF_LINEBREAK = $fd; + IPF_SPACE = $fe; + IPF_ESC = $ff; + + // Subescape codes of + HPART_DEFINE = 0; + HPART_PT_HDREF = 1; + HPART_PT_FNREF = 2; + HPART_PT_SPREF = 3; + HPART_HDREF = 4; + HPART_FNREF = 5; + HPART_SPREF = 6; + HPART_LAUNCH = 7; + HPART_PT_LAUNCH = 8; + HPART_INFORM = 9; + HPART_PT_INFORM = 10; + // ?? 11 ?? + HPART_EXTERN_PT_HDREF = 12; + HPART_EXTERN_PT_SPREF = 13; + HPART_EXTERN_HDREF = 14; + HPART_EXTERN_SPREF = 15; + HPART_GLOBAL_HDREF = 16; + HPART_GLOBAL_PT_HDREF = 17; + +Implementation + +Initialization +End. diff --git a/docview/src/IPFFileFormatUnit.pas b/docview/src/IPFFileFormatUnit.pas new file mode 100644 index 00000000..b954f4c8 --- /dev/null +++ b/docview/src/IPFFileFormatUnit.pas @@ -0,0 +1,493 @@ +Unit IPFFileFormatUnit; + +{$mode objfpc}{$H+} + +interface + +// Definition of IPF file header and other structures + +uses + SysUtils; + +type + uint32 = longword; + uint16 = word; + uint8 = byte; + pUInt16 = ^uint16; + pUInt32 = ^uint32; + pUInt8 = ^uint8; + Unsigned_31 = 0 .. (1 shl 31) - 1; // 31-bit type + Unsigned_4 = 0 .. (1 shl 4) - 1; // 4-bit type + Unsigned_1 = 0 .. (1 shl 1) - 1; // 1-bit type + + + PCharArray = packed array[ 0..0 ] of PCHar; + UInt32Array = packed array[ 0..0 ] of UInt32; + UInt16Array = packed array[ 0..0 ] of UInt16; + UInt8Array = packed array[ 0..0 ] of UInt8; + + PCharArrayPointer = ^PCharArray; + UInt32ArrayPointer = ^UInt32Array; + UInt16ArrayPointer = ^UInt16Array; + UInt8ArrayPointer = ^UInt8Array; + + TBooleanArray = array[ 0..0 ] of boolean; + BooleanArrayPointer = ^TBooleanArray; + + + EHelpFileException = class( Exception ) + end; + + + EWindowsHelpFormatException = class( Exception ) + end; + + + TProgressCallback = procedure(n, outof: integer; AMessage: string) of object; + + +const + ErrorCorruptHelpFile = 'Corrupt help file, or something similar'; + +const + INF_HEADER_ID = 'HSP'; + +Type + THelpFileHeader = packed record + ID: array[0..2] of ansichar; // ID magic word "HSP" + flags: uint8; // probably a flag word... + // [0x01] bit 0: set if INF style file + // [0x10] bit 4: set if HLP style file + // patching this byte allows reading HLP files + // using the VIEW command, while help files + // seem to work with INF settings here as well. + hdrsize: uint16; // total size of header + version_hi: uint8; + version_lo: uint8; + ntoc: uint16; // number of entries in the tocarray + tocstart: uint32; // file offset of the start of the toc + toclen: uint32; // number of bytes in file occupied by the toc + tocoffsetsstart: uint32; // file offset of the start of array of toc offsets + nres: uint16; // number of panels with ressource numbers + resstart: uint32; // 32 bit file offset of ressource number table + nname: uint16; // number of panels with textual name + namestart: uint32; // 32 bit file offset to panel name table + nindex: uint16; // number of index entries + indexstart: uint32; // 32 bit file offset to index table + indexlen: uint32; // size of index table + icmdCount: uint16; // number of icmd index items + icmdOffset: uint32; // file offset to icmd index items + icmdSize: uint32; // size of icmd index table + searchstart: uint32; // 31 bit file offset of full text search table + // Note: top bit indicates 32 bit search record! + searchlen: uint32; // size of full text search table + nslots: uint16; // number of "slots" + slotsstart: uint32; // file offset of the slots array + dictlen: uint32; // number of bytes occupied by the "dictionary" + ndict: uint16; // number of entries in the dictionary + dictstart: uint32; // file offset of the start of the dictionary + imgstart: uint32; // file offset of image data + maxCVTIndex: byte; // highest index inside panel's local dictionary, + // always seems to be 245 + nlsstart: uint32; // 32 bit file offset of NLS table + nlslen: uint32; // size of NLS table + extstart: uint32; // 32 bit file offset of extended data block + reserved: array[ 0..11 ] of byte; // for future use. set to zero. + title: array[ 0..47 ] of ansichar; // ASCII title of database + end; + TPHelpFileHeader = ^THelpFileHeader; + + TExtendedHelpFileHeader = packed record + NumFontEntry: uint16; // FONT TABLE: Number entries + FontTableOffset: uint32; // FONT TABLE: Offset in file + NumDataBase: uint16; // DATA BASE: Number of files + DataBaseOffset: uint32; // DATA BASE: Offset in file + DataBaseSize: uint32; // DATA BASE: Size in bytes + EntryInGNameTable: uint16; // GLOBAL NAMES: Number entries + HelpPanelGNameTblOffset: uint32; // GLOBAL NAMES: Offset in file + StringsOffset: uint32; // STRINGS : Offset in file + StringsSize: uint16; // STRINGS : Total bytes of all strings + ChildPagesOffset: uint32; // CHILDPAGES : Offset in file + ChildPagesSize: uint32; // CHILDPAGES : Total bytes of all strings + NumGIndexEntry: uint32; // Total number of Global Index items + CtrlOffset: uint32; // CTRL BUTTONS : offset in file + CtrlSize: uint32; // CTRL BUTTONS : size in bytes + Reserved: array[0..3] of uint32; // For future use. Set to zero + end; + TPExtendedHelpFileHeader = ^TExtendedHelpFileHeader; + +Type + TTOCEntryStart = packed record + length: uint8; // length of the entry including this byte (but not including extended data) + flags: uint8; // flag byte, description folows (MSB first) + // bit8 haschildren; // following nodes are a higher level + // bit7 hidden; // this entry doesn't appear in VIEW.EXE's + // presentation of the toc + // bit6 extended; // extended entry format + // bit5 stuff; // ?? + // int4 level; // nesting level + numSlots: uint8; // number of "slots" occupied by the text for + // this toc entry + end; + pTTOCEntryStart = ^TTOCEntryStart; + + TExtendedTOCEntry = packed record + w1: uint8; + // bit 3: Window controls are specified + // bit 2: Viewport + // bit 1: Size is specified. + // bit 0: Position is specified. + w2: uint8; + // bit 3: + // bit 2: Group is specified. + // bit 1 + // bit 0: Clear (all windows before display) + end; + pExtendedTOCEntry = ^TExtendedTOCEntry; + + TTOCEntryOffsetArray = packed array[ 0..0 ] of uint32; + pTTOCEntryOffsetArray = ^TTOCEntryOffsetArray; + +const + TOCEntryExtended = $20; { extended entry format } + TOCEntryHidden = $40; { this entry doesn't appear in VIEW.EXE's presentation of the toc } + TOCEntryHasChildren = $80; { following nodes are a higher level } + TOCEntryLevelMask = $0f; + +type + THelpXYPair = packed record + Flags: uint8; + X: uint16; + Y: uint16; + end; + pHelpXYPair = ^THelpXYPair; + + TSlotHeader = packed record + stuff: uint8; // always 0?? + localdictpos: uint32; // file offset of the local dictionary + nlocaldict: uint8; // number of entries in the local dict + ntext: uint16; // number of bytes in the text + end; + pSlotHeader = ^TSlotHeader; + + THelpFontSpec = packed record + FaceName: array[ 0..32 ] of ansichar; + Height: uint16; + Width: uint16; + Codepage: uint16; + end; + pTHelpFontSpec = ^THelpFontSpec; + + TNlsHeader = packed record + NlsSize: uint16; + NlsType: uint8; + NlsFormat: uint8; + end; + + TNlsCountryDef = packed record + { if the following is true then... + NlsHeader.size = 10 + NlsHeader.type = NLSRecType.CONTROL + NlsHeader.format = 0 + } + Value: uint16; // =256 + Code: uint16; // country code + Page: uint16; // code page + Reserved: uint16; + end; + + // Single-byte character set + TSbcsNlsGrammerDef = packed record + { if the following is true then... + NlsHeader.size = 36 + NlsHeader.type = NLSRecType.WORD || NLSRecType.GRAPHIC + NlsHeader.format = 0 + } + bits: array[0..31] of uint8; // high-order bits first + end; + + TPanelControls = packed record + ControlCount: uint16; // number of ControlDef records + GroupCount: uint16; // number of GroupDef records + GroupIndex: uint16; // for cover page + Reserved: uint16; + end; + + TControlDef = packed record + CtrlType: uint16; // type of control + ResourceID: uint16; // resource id (panel) it belongs to + { variable length data follows, contains button text } + // DictString: array of char; + end; + + TControlGroupDef = packed record + Count: uint16; // number of indexes into ControlDef records + { variable length data follows } + // index[count] of type uint16 + end; + +// List of IPF escape codes. + +const + // Basic byte codes + IPF_END_PARA = $fa; + IPF_CENTER = $fb; + IPF_INVERT_SPACING = $fc; + IPF_LINEBREAK = $fd; + IPF_SPACE = $fe; + IPF_ESC = $ff; // followed by one of the ecXXX codes below + + // FF XX + ecSetLeftMargin = $02; + ecHighlight1 = $04; // hp1,2,3,5,6,7 + ecLinkStart = $05; + ecFootnoteLinkStart = $07; + ecLinkEnd = $08; + ecStartCharGraphics = $0b; + ecEndCharGraphics = $0c; + ecHighlight2 = $0d; // hp4,8,9 + ecImage = $0e; + ecLinkedImage = $0f; + ecProgramLink = $10; + ecSetLeftMarginNewLine = $11; + ecSetLeftMarginFit = $12; + ecForegroundColor = $13; + ecBackgroundColor = $14; + ecFontChange = $19; + ecStartLines = $1a; + ecEndLines = $1b; + ecSetLeftMarginHere = $1c; + ecStartLinkByResourceID = $1d; + ecExternalLink = $1f; + + // Subescape codes of + HPART_DEFINE = 0; + HPART_PT_HDREF = 1; + HPART_PT_FNREF = 2; + HPART_PT_SPREF = 3; + HPART_HDREF = 4; + HPART_FNREF = 5; + HPART_SPREF = 6; + HPART_LAUNCH = 7; + HPART_PT_LAUNCH = 8; + HPART_INFORM = 9; + HPART_PT_INFORM = 10; + // ?? 11 ?? + HPART_EXTERN_PT_HDREF = 12; + HPART_EXTERN_PT_SPREF = 13; + HPART_EXTERN_HDREF = 14; + HPART_EXTERN_SPREF = 15; + HPART_GLOBAL_HDREF = 16; + HPART_GLOBAL_PT_HDREF = 17; + +// ----------------------------------------------------------- +// Operations on Int32 arrays, used for searching +// These could be optimised heavily if needed. +procedure AllocUInt32Array( Var pArray: UInt32ArrayPointer; + Size: longint ); +procedure FreeUInt32Array( Var pArray: UInt32ArrayPointer; + Size: longint ); + +procedure FillUInt32Array( pArray: UInt32ArrayPointer; + Size: longint; + Value: UInt32 ); + +procedure AddUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +// Dest = Dest + source * Multiplier +procedure AddMultConstUInt32Array( pSource: UInt32ArrayPointer; + Multiplier: longint; + pDest: UInt32ArrayPointer; + Size: longint ); + +procedure AndUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +// If both source and dest > 0 then +// add source to dest +procedure AndAddUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +// if Source > 0 then dest is set to 0 +procedure AndNotUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +// dest = dest or source; +// if source > 0 then set dest to > 0 +procedure OrUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +// if source = 0 then dest set to >0 +procedure NotOrUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +procedure CopyUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); + +procedure ClearUInt32Array( pArray: UInt32ArrayPointer; + Size: longint ); +procedure SetUInt32Array( pArray: UInt32ArrayPointer; + Size: longint ); + +// returns the result of ORing every array element. +// Can be useful for debugging e.g. seeing at a glance +// if any element is non-zero +function OrAllUInt32Array( pArray: UInt32ArrayPointer; + Size: longint ): longint; + + +Implementation + + +// Operations on int32 arrays +// ----------------------------------------------------------- + +procedure AllocUInt32Array( Var pArray: UInt32ArrayPointer; + Size: longint ); +begin + GetMem( pArray, + Size + * sizeof( UInt32 ) ); +end; + +procedure FreeUInt32Array( Var pArray: UInt32ArrayPointer; + Size: longint ); +begin + FreeMem( pArray, + Size + * sizeof( UInt32 ) ); +end; + +// This is a nice fast implementation of filling an +// array of dwords (Int32/longword) +procedure FillUInt32Array( pArray: UInt32ArrayPointer; + Size: longint; + Value: UInt32 ); +var + i: integer; +begin + assert( Size > 0 ); + if Size < 1 then + Exit; + for i := 0 to Size-1 do + begin + pArray^[i] := Value; + end; +end; + +procedure ClearUInt32Array( pArray: UInt32ArrayPointer; + Size: longint ); +begin + FillUInt32Array( pArray, Size, 0 ); +end; + +procedure SetUInt32Array( pArray: UInt32ArrayPointer; + Size: longint ); +begin + FillUInt32Array( pArray, Size, $ffffffff ); +end; + +procedure AddUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + inc( pDest^[ i ], pSource^[ i ] ); +end; + +procedure AddMultConstUInt32Array( pSource: UInt32ArrayPointer; + Multiplier: longint; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + inc( pDest^[ i ], pSource^[ i ] * Multiplier ); +end; + +procedure OrUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + pDest^[ i ] := pDest^[ i ] or pSource^[ i ]; +end; + +procedure CopyUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +begin + Move(pSource^, PDest^, Size * SizeOf(LongInt)); +end; + +procedure NotOrUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + if pSource^[ i ] = 0 then + pDest^[ i ] := 1; +end; + +procedure AndUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + pDest^[ i ] := pDest^[ i ] and pSource^[ i ]; +end; + +procedure AndAddUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + if ( pSource^[ i ] > 0 ) + and ( pDest^[ i ] > 0 ) then + inc( pDest^[ i ], pSource^[ i ] ) + else + pDest^[ i ] := 0; +end; + +procedure AndNotUInt32Array( pSource: UInt32ArrayPointer; + pDest: UInt32ArrayPointer; + Size: longint ); +var + i: longint; +begin + for i := 0 to Size - 1 do + if pSource^[ i ] > 0 then + pDest^[ i ] := 0; +end; + +function OrAllUInt32Array( pArray: UInt32ArrayPointer; + Size: longint ): longint; +var + i: longint; +begin + Result := 0; + for i := 0 to Size - 1 do + Result := Result or pArray^[ i ]; +end; + + +end. diff --git a/docview/src/NewViewConstantsUnit.pas b/docview/src/NewViewConstantsUnit.pas new file mode 100644 index 00000000..4e191b15 --- /dev/null +++ b/docview/src/NewViewConstantsUnit.pas @@ -0,0 +1,28 @@ +Unit NewViewConstantsUnit; + +{$mode objfpc}{$H+} + +// NewView - a new OS/2 Help Viewer +// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) +// Copyright 2006-2009 Ronald Brill (rbri at rbri dot de) +// This software is released under the Gnu Public License - see readme.txt + +// Common used constants for NewView + +Interface + +const + PARAM_LINK_NOTE = 'note'; + PARAM_LINK_PROGRAM = 'program'; + PARAM_LINK_URL = 'url'; + PARAM_LINK_EXTERNAL = 'external'; + + PRGM_EXPLORER = 'explore'; // web explorer + PRGM_NETSCAPE = 'netscape'; + PRGM_MOZILLA = 'mozilla'; + PRGM_FIREFOX = 'firefox'; + + +Implementation + +End. diff --git a/docview/src/SearchTable.pas b/docview/src/SearchTable.pas new file mode 100644 index 00000000..49c67ff5 --- /dev/null +++ b/docview/src/SearchTable.pas @@ -0,0 +1,298 @@ +Unit SearchTable; + +{$mode objfpc}{$H+} + +// NewView - a new OS/2 Help Viewer +// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) +// This software is released under the Gnu Public License - see readme.txt + +Interface + +uses + Classes, IPFFileFormatUnit; + +// Code to read and use IPF search tables +// NB The RLE decompression was arrived at by trial and error +// it seems to be correct but it's difficult to test. + +type + TSearchTable = class(TObject) + protected + _Data: pointer; + _Entries: TList; // pointers to panel flag records + _RecordLengthIs16Bit: boolean; + _DictionaryCount: longint; + _TopicCount: longint; + + procedure ReadEntries; + + Procedure Check1ByteOfFlags( b: byte; + StartingIndex: longint; + Results: UInt32ArrayPointer ); + + procedure DoRLESearch( p: pbyte; + pDataEnd: pointer; + Results: UInt32ArrayPointer ); + + public + constructor Create( Data: pointer; + RecordLengthIs16Bit: boolean; + DictionaryCount: longint; + TopicCount: longint ); + destructor Destroy; override; + + // Sets Results to 1 for occurrences of DictIndex + procedure Search( DictIndex: uint16; + Results: UInt32ArrayPointer ); + + end; + +Implementation + +constructor TSearchTable.Create( Data: pointer; + RecordLengthIs16Bit: boolean; + DictionaryCount: longint; + TopicCount: longint ); +begin + _Data := Data; + _RecordLengthIs16Bit := RecordLengthIs16Bit; + _Entries := TList.Create; + _DictionaryCount := DictionaryCount; + _TopicCount := TopicCount; + ReadEntries; +end; + +destructor TSearchTable.Destroy; +begin + _Entries.Destroy; +end; + +procedure TSearchTable.ReadEntries; +var + pWordRecord: pointer; + RecordLen: uint16; + WordIndex: uint16; +begin + pWordRecord:= _Data; + + for WordIndex:= 0 to _DictionaryCount - 1 do + begin + _Entries.Add( pWordRecord ); + + if _RecordLengthIs16Bit then + RecordLen:= pUInt16( pWordRecord )^ + else // 8 bit + RecordLen:= pUInt8( pWordRecord )^; + inc( pWordRecord, RecordLen ); + end; +end; + + +// Search table decompression + +// Looks through a single byte of 8 flags, given by b, +// and updates topic entries within results for any flags +// that are set. +Procedure TSearchTable.Check1ByteOfFlags( b: byte; + StartingIndex: longint; + Results: UInt32ArrayPointer ); +var + TopicIndex: longint; +begin + TopicIndex:= StartingIndex; + while b > 0 do + begin + if b and $80 > 0 then + Results^[ TopicIndex ] := 1; + inc( TopicIndex ); + b:= b shl 1; + end; +end; + +// Decompress RLE compressed data starting at p, +// running til pDataEnd. Update topic entries in Results. +procedure TSearchTable.DoRLESearch( p: pbyte; + pDataEnd: pointer; + Results: UInt32ArrayPointer ); +var + TopicIndex: integer; + + N: integer; + thebyte: byte; + byte1, byte2: byte; +begin + assert( pbyte( p )^ = 1, 'Unexpected RLE type' ); + inc( p ); // skip header, always 1? + + TopicIndex:= 0; + + while p < pDataEnd do + begin + thebyte:= p^; + inc( p ); + + if thebyte = $80 then + begin + // escape + thebyte := p^; + inc( p ); + + if thebyte = 0 then + begin + // 16 bit repeat of zeroes?? + N := pUInt16( p )^ + 1; + inc( p, 2 ); + inc( TopicIndex, N ); + end + else + begin + // n+1 repeats of next 2 bytes??? + N := thebyte + 1; + byte1 := p^; + inc( p ); + byte2 := p^; + inc( p ); + while N > 0 do + begin + Check1ByteOfFlags( byte1, + TopicIndex, + Results ); + inc( TopicIndex, 8 ); + Check1ByteOfFlags( byte2, + TopicIndex, + Results ); + inc( TopicIndex, 8 ); + dec( N ); + end; + end; + end + else + begin + N:= thebyte and $7f + 1; + + if thebyte and $80 > 0 then + begin + // literal data + while N > 0 do + begin + Check1ByteOfFlags( p^, + TopicIndex, + Results ); + inc( TopicIndex, 8 ); + inc( p ); + dec( N ); + end; + end + else + begin + // repeat of next byte + thebyte := p^; + inc( p ); + while N > 0 do + begin + Check1ByteOfFlags( thebyte, + TopicIndex, + Results ); + inc( TopicIndex, 8 ); + dec( N ); + end; + end; + end; + end; +end; + +// This function finds uses of the given word (DictIndex) +// using the search table. Results[ topic ] is set to +// non-zero for topics which contain the word. +procedure TSearchTable.Search( DictIndex: uint16; + Results: UInt32ArrayPointer ); +var + TopicIndex: integer; + pWordRecord: pointer; + RecordLen: uint16; + CompressionCode: uint8; + pData: pointer; + pDataEnd: pointer; + Flags: uint8; +begin + FillUInt32Array( Results, _TopicCount, 0 ); + pWordRecord:= _Entries[ DictIndex ]; + + // Check search table format + if _RecordLengthIs16Bit then + begin + RecordLen:= pUInt16( pWordRecord )^; + CompressionCode:= pUInt8( pWordRecord + 2 )^; + pData:= pWordRecord + 3; + end + else // 8 bit + begin + RecordLen:= pUInt8( pWordRecord )^; + CompressionCode:= pUInt8( pWordRecord + 1 )^; + pData:= pWordRecord + 2; + end; + + // Decompress the search table for this word + pDataEnd:= pWordRecord + RecordLen; + case CompressionCode of + 0: // word not used anywhere. + ; + + 1: // used in all panels + FillUInt32Array( Results, _TopicCount, 1 ); + + 2: // RLE + begin + DoRLESearch( pData, + pDataEnd, + Results ); + end; + + 3: // list of topics containing word + begin + while pData < pDataEnd do + begin + TopicIndex:= pUInt16( pData )^; + Results^[ TopicIndex ] := 1; + inc( pData, 2 ); + end; + end; + + 4: // list of topics NOT containing word + begin + FillUInt32Array( Results, _TopicCount, 1 ); + + while pData < pDataEnd do + begin + TopicIndex:= pUInt16( pData )^; + Results^[ TopicIndex ] := 0; + inc( pData, 2 ); + end; + end; + + 5, // compressed by truncating bit stream at last byte containing a set bit. + 6: // same as above but starting at non-zero byte + begin + if CompressionCode = 5 then + TopicIndex:= 0 + else + begin + TopicIndex:= pUInt16( pData )^ * 8; + inc( pData, 2 ); + end; + + while pData < pDataEnd do + begin + Flags:= pUInt8( pData )^; + Check1ByteOfFlags( Flags, + TopicIndex, + Results ); + inc( TopicIndex, 8 ); + inc( pData ); + end; + end; + end; +end; + + +end. diff --git a/docview/src/SearchUnit.pas b/docview/src/SearchUnit.pas new file mode 100644 index 00000000..f7a8eb8e --- /dev/null +++ b/docview/src/SearchUnit.pas @@ -0,0 +1,567 @@ +Unit SearchUnit; + +{$mode objfpc}{$H+} + +// NewView - a new OS/2 Help Viewer +// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) +// This software is released under the Gnu Public License - see readme.txt + +Interface + +// Contains code to search help files. + +uses + Classes, + HelpFile, + TextSearchQuery, + IPFFileFormatUnit; + +const + // match weightings + mwOnlyTitleWord = 200; + mwFirstTitleWord = 50; + mwTitleWord = 20; + + mwOnlyIndexWord = 100; + mwFirstIndexWord = 20; + mwIndexWord = 10; + mwTopicTextWord = 1; + + // best case match weighting of a word + mwExactWord = 20; + + +// note on weightings. The title/index weightings +// are multipled by word weightings. +// Topic text matches are equal to word weighting +// times word weighting. + +procedure SearchHelpFile( HelpFile: THelpFile; + Query: TTextSearchQuery; + Results: TList; + WordSequences: TList ); + +// clear a lsit of word sequences (as produced by above) +procedure ClearWordSequences( WordSequences: TList; DictionaryCount: longint ); + +Implementation + +uses + SysUtils + ,HelpTopic + ,CompareWordUnit + ,nvUtilities + ,ACLStringUtility + ; + +type + TSearchType = ( stGeneral, stStarts, stExactMatch, stEnds ); + +procedure ClearWordSequence( WordSequence: TList; + DictionaryCount: longint ); +var + StepIndex: longint; + DictionaryRelevances: UInt32ArrayPointer; +begin + for StepIndex := 0 to WordSequence.Count - 1 do + begin + DictionaryRelevances := WordSequence[ StepIndex ]; + FreeUInt32Array( DictionaryRelevances, DictionaryCount ); + end; + WordSequence.Clear; +end; + +procedure ClearWordSequences( WordSequences: TList; DictionaryCount: longint ); +var + SequenceIndex: longint; + lWordSequence: TList; +begin + for SequenceIndex := 0 to WordSequences.Count - 1 do + begin + lWordSequence := TList(WordSequences[ SequenceIndex ]); + ClearWordSequence( lWordSequence, DictionaryCount ); + lWordSequence.Destroy; + end; + WordSequences.Clear; +end; + +// given a search word which is known to matche Reference word, +// return the relevance +function MatchedWordRelevance( const SearchWord: string; + const ReferenceWord: string ): longint; +begin + Result := mwExactWord + * Length( SearchWord ) + div Length( ReferenceWord ); + if Result = 0 then + Result := 1; +end; + +// Compares the given search word against the given +// reference word. Returns a value indicating how well the +// search word matches, 0 = not at all. +function CompareWord( const SearchWord: string; + const ReferenceWord: string ): longint; +var + OccurrencePos: longint; +begin + Result := 0; + OccurrencePos := CaseInsensitivePos( SearchWord, ReferenceWord ); + if OccurrencePos = 0 then + begin + // no match + exit; + end; + + Result := MatchedWordRelevance( SearchWord, ReferenceWord ); +end; + +// Search the help file dictionary for words that match +// the given search word. Partial matches are considered. +// Results returns the matching word indexes. +procedure SearchDictionary( HelpFile: THelpFile; + SearchWord: string; + Results: UInt32ArrayPointer ); +var + DictIndex: integer; + DictWord: string; +begin + for DictIndex := 0 to HelpFile.DictionaryCount - 1 do + begin + DictWord := HelpFile.DictionaryWords[ DictIndex ]; + Results^[ DictIndex ] := CompareWord( SearchWord, DictWord ); + end; +end; + +// Search the help file dictionary for words that +// match the given search word exactly (except for case-insensitive) +procedure SearchDictionaryExact( HelpFile: THelpFile; + SearchWord: string; + Results: UInt32ArrayPointer ); +var + DictIndex: integer; + DictWord: string; +begin + FillUInt32Array( Results, HelpFile.DictionaryCount, 0 ); + + for DictIndex := 0 to HelpFile.DictionaryCount - 1 do + begin + DictWord := HelpFile.DictionaryWords[ DictIndex ]; + if SameText( SearchWord, DictWord ) then + Results^[ DictIndex ] := mwExactWord; + end; +end; + +// Search the help file dictionary for words that +// start with the given word +procedure SearchDictionaryStarts( HelpFile: THelpFile; + SearchWord: string; + Results: UInt32ArrayPointer ); +var + DictIndex: integer; + DictWord: string; +begin + FillUInt32Array( Results, HelpFile.DictionaryCount, 0 ); + + for DictIndex := 0 to HelpFile.DictionaryCount - 1 do + begin + DictWord := HelpFile.DictionaryWords[ DictIndex ]; + if StrStartsWithIgnoringCase(DictWord, SearchWord) then + Results^[ DictIndex ] := MatchedWordRelevance( SearchWord, DictWord ); + end; +end; + +// Search the help file dictionary for words that +// end with the given word +procedure SearchDictionaryEnds( HelpFile: THelpFile; + SearchWord: string; + Results: UInt32ArrayPointer ); +var + DictIndex: integer; + DictWord: string; +begin + FillUInt32Array( Results, HelpFile.DictionaryCount, 0 ); + + for DictIndex := 0 to HelpFile.DictionaryCount - 1 do + begin + DictWord := HelpFile.DictionaryWords[ DictIndex ]; + if StrEndsWithIgnoringCase( SearchWord, DictWord ) then + Results^[ DictIndex ] := MatchedWordRelevance( SearchWord, DictWord ); + end; +end; + +// Search titles of topics for given searchword +procedure SearchTopicTitles( HelpFile: THelpFile; + SearchWord: string; + Results: UInt32ArrayPointer ); +var + TopicIndex: longint; + Title: string; + TitleWord: string; + Topic: TTopic; + TitleWordIndex: longint; + WordRelevance: longint; + TitleWordRelevance: longint; + tmpTitleWords : TStrings; + i : integer; +begin + tmpTitleWords := TStringList.Create; + + // Search topic titles + for TopicIndex := 0 to HelpFile.TopicCount - 1 do + begin + Topic := HelpFile.Topics[ TopicIndex ]; + Title:= Topic.Title; + TitleWordIndex := 0; + + tmpTitleWords.Clear; + StrExtractStringsQuoted(tmpTitleWords, Title); + + for i := 0 to tmpTitleWords.count-1 do + begin + TitleWord := tmpTitleWords[i]; + + WordRelevance := CompareWord( SearchWord, + TitleWord ); + if WordRelevance > 0 then + begin + if TitleWordIndex = 0 then + begin + // matching the first word is best + if i = tmpTitleWords.count-1 then + begin + // in fact it's the only word + TitleWordRelevance := mwOnlyTitleWord * WordRelevance + end + else + TitleWordRelevance := mwFirstTitleWord * WordRelevance + end + else + begin + TitleWordRelevance := mwTitleWord * WordRelevance; + end; + inc( Results^[ Topic.Index ], TitleWordRelevance ); + end; + inc( TitleWordIndex ); + end; + end; + tmpTitleWords.Free; +end; + +// Search index entries for given searchword +procedure SearchIndex( HelpFile: THelpFile; SearchWord: string; Results: UInt32ArrayPointer ); +var + IndexIndex: longint; + IndexEntry: string; + IndexEntryWord: string; + tmpTopic: TTopic; + IndexEntryWordIndex: longint; + WordRelevance: longint; + IndexEntryWordRelevance: longint; + tmpIndexWords : TStrings; + i : integer; +begin + tmpIndexWords := TStringList.Create; + + for IndexIndex := 0 to HelpFile.Index.Count - 1 do + begin + IndexEntry := HelpFile.Index.GetLabels[IndexIndex]; + IndexEntryWordIndex := 0; + + tmpIndexWords.Clear; + StrExtractStringsQuoted(tmpIndexWords, IndexEntry); + + for i := 0 to tmpIndexWords.count-1 do + begin + IndexEntryWord := tmpIndexWords[i]; + + WordRelevance := CompareWord( SearchWord, IndexEntryWord ); + if WordRelevance > 0 then + begin + if IndexEntryWordIndex = 0 then + begin + // matching the first word is best + if i = tmpIndexWords.count-1 then + begin + // in fact it's the only word + IndexEntryWordRelevance := mwOnlyIndexWord * WordRelevance + end + else + IndexEntryWordRelevance := mwFirstIndexWord * WordRelevance + end + else + begin + IndexEntryWordRelevance := mwIndexWord * WordRelevance; + end; + tmpTopic := HelpFile.Index.getTopic(IndexIndex); + inc( Results^[ tmpTopic.Index ], IndexEntryWordRelevance ); + end; + inc( IndexEntryWordIndex ); + end; + end; + + tmpIndexWords.Free; +end; + +// ------------------------------------------------------ + +// Master search function. Given a search query, +// searches topic text, titles, index entries. +// Matching topics are added to TList, with their +// SearchRelevance set appropriately. +procedure SearchHelpFile( HelpFile: THelpFile; + Query: TTextSearchQuery; + Results: TList; + WordSequences: TList ); +var + TopicCount: longint; + Topic: TTopic; + TopicIndex: longint; + TermIndex: longint; + Term: TSearchTerm; + + DictionaryRelevances: UInt32ArrayPointer; + + TopicsMatchingDictWord: UInt32ArrayPointer; // flags + TopicsMatchingTermPart: UInt32ArrayPointer; // flags + TopicsMatchingTerm: UInt32ArrayPointer; // flag then relevances + TopicRelevances: UInt32ArrayPointer; + TopicsExcluded: UInt32ArrayPointer; + + TopicRelevanceForTerm: longint; + + WordRelevance: longint; + DictIndex: longint; + + TermPartIndex: longint; + TermPart: string; + + s: string; + + TermWordSequence: TList; +begin + if HelpFile.SearchTable = nil then + begin + exit; + end; + + // Reset flags per topic + TopicCount := HelpFile.TopicCount; + + // Get memory for topic relevance arrays + + AllocUInt32Array( TopicsMatchingDictWord, + TopicCount ); + AllocUInt32Array( TopicsMatchingTermPart, + TopicCount ); + AllocUInt32Array( TopicsMatchingTerm, + TopicCount ); + AllocUInt32Array( TopicRelevances, // functions as a flag and a cumulative relevance + TopicCount ); + AllocUInt32Array( TopicsExcluded, // Exclusions are treated as boolean only + TopicCount ); + + FillUInt32Array( TopicRelevances, TopicCount, 0); + FillUInt32Array( TopicsExcluded, TopicCount, 0); + + for TermIndex := 0 to Query.TermCount - 1 do + begin + Term := Query.Term[ TermIndex ]; + + LogEvent(LogSearch, 'Searching for term "' + + Term.Text + + '", ' + + IntToStr( Term.Parts.Count ) + + ' parts' ); + + // look thru all parts of the term. eg. CAKE_SAUSAGE + + TermWordSequence := TList.Create; + + if WordSequences <> nil then + if Term.CombineMethod <> cmExcluded then + // this term is an inclusive one, so we want to remember the matches + WordSequences.Add( TermWordSequence ); + + for TermPartIndex := 0 to Term.Parts.Count - 1 do + begin + TermPart := Term.Parts[ TermPartIndex ]; + + LogEvent(LogSearch, ' Searching for [' + TermPart + ']' ); + + AllocUInt32Array( DictionaryRelevances, + HelpFile.DictionaryCount ); + + TermWordSequence.Add( DictionaryRelevances ); + + // Search the dictionary for matches. + // alpha numeric match + + if Term.Parts.Count = 1 then + // general match allowing all kinds of partial matches + SearchDictionary( HelpFile, + TermPart, + DictionaryRelevances ) + + else if TermPartIndex = 0 then + // first term part: word must match end of a topic word e.g. must end in "cake" + SearchDictionaryEnds( HelpFile, + TermPart, + DictionaryRelevances ) + + else if TermPartIndex = Term.Parts.Count - 1 then + // last term part: word must match start of a topic word e.g. must start with "sausage" + SearchDictionaryStarts( HelpFile, + TermPart, + DictionaryRelevances ) + + else + // intermediate term part: word must match exactly e.g. must be "_" + SearchDictionaryExact( HelpFile, + TermPart, + DictionaryRelevances ); + + // For each word in the dictionary that matches + // this search term part, search topic texts + + LogEvent(LogSearch, ' Dictionary search done' ); + ClearUInt32Array( TopicsMatchingTermPart, + TopicCount ); + + for DictIndex := 0 to HelpFile.DictionaryCount - 1 do + begin + WordRelevance := DictionaryRelevances^[ DictIndex ]; + if WordRelevance > 0 then + begin + // Search for occurrences of this word + // within the text of topics + HelpFile.SearchTable.Search( DictIndex, + TopicsMatchingDictWord ); + + // debug + s := HelpFile.DictionaryWords[ DictIndex ]; + // TopicRelevancesForDictWord now contains 1 + // for topics that contain this word. + + OrUInt32Array( TopicsMatchingDictWord, + TopicsMatchingTermPart, + TopicCount ); + end + end; + + LogEvent(LogSearch, 'Topic searches done' ); + + if TermPartIndex = 0 then + // first part, just copy + CopyUInt32Array( TopicsMatchingTermPart, + TopicsMatchingTerm, + TopicCount ) + else + // and with previous term part results + AndUInt32Array( TopicsMatchingTermPart, + TopicsMatchingTerm, + TopicCount ); + + // loop for next term part (IPF word) + end; + + // Now we have searched the dictionary and worked out matching topics + // for all parts of the term. Now combine all together + + LogEvent(LogSearch, 'Checking for sequences' ); + for TopicIndex := 0 to TopicCount - 1 do + begin + if TopicsMatchingTerm^[ TopicIndex ] > 0 then + begin + Topic := HelpFile.Topics[ TopicIndex ]; + // Topic text contained a match for the all the parts + // of the term. + // Now we need to: + // - verify that they actually occur all in a sequence (if it's a multi-part term) + // - count occurrences for relevance. + + TopicRelevanceForTerm := + Topic.SearchForWordSequences( TermWordSequence, + false ); // don't stop at first match + + TopicRelevanceForTerm := + TopicRelevanceForTerm div Term.Parts.Count; // divide to bring back into scale + + TopicsMatchingTerm^[ TopicIndex ] := TopicRelevanceForTerm; + + end; + end; + + if WordSequences = nil then + begin + // we don't need to keep the sequence + ClearWordSequence( TermWordSequence, HelpFile.DictionaryCount ); + TermWordSequence.Free; + end; + + // Search titles and index + + LogEvent(LogSearch, ' Searching titles' ); + SearchTopicTitles( HelpFile, Term.Text, TopicsMatchingTerm ); + + LogEvent(LogSearch, ' Searching index' ); + SearchIndex( HelpFile, Term.Text, TopicsMatchingTerm ); + + LogEvent(LogSearch, ' Combining' ); + case Term.CombineMethod of + cmOptional: + AddUInt32Array( TopicsMatchingTerm, + TopicRelevances, + TopicCount ); + + cmRequired: + begin + // if zero then add to exclusions + NotOrUInt32Array( TopicsMatchingTerm, + TopicsExcluded, + TopicCount ); + + AddUInt32Array( TopicsMatchingTerm, + TopicRelevances, + TopicCount ); + end; + + cmExcluded: + OrUInt32Array( TopicsMatchingTerm, + TopicsExcluded, + TopicCount ); + end; + +// Term.ClearMatches; + + // loop for next term... + end; + + LogEvent(LogSearch, 'Search completed, converting to list' ); + + // Now convert to list form. + + for TopicIndex := 0 to TopicCount - 1 do + begin + if TopicsExcluded^[ TopicIndex ] = 0 then + begin + Topic := HelpFile.Topics[ TopicIndex ]; + Topic.SearchRelevance := TopicRelevances^[ TopicIndex ]; + if Topic.SearchRelevance > 0 then + begin + Results.Add( Topic ); + end; + end; + end; + + LogEvent(LogSearch, 'Freeing arrays' ); + FreeUInt32Array( TopicRelevances, TopicCount ); + FreeUInt32Array( TopicsExcluded, TopicCount ); + FreeUInt32Array( TopicsMatchingTerm, TopicCount ); + FreeUInt32Array( TopicsMatchingTermPart, TopicCount ); + FreeUInt32Array( TopicsMatchingDictWord, TopicCount ); + + LogEvent(LogSearch, 'Done' ); +end; + +End. diff --git a/docview/src/SettingsUnit.pas b/docview/src/SettingsUnit.pas new file mode 100644 index 00000000..82a3aaf7 --- /dev/null +++ b/docview/src/SettingsUnit.pas @@ -0,0 +1,528 @@ +Unit SettingsUnit; + +{$mode objfpc}{$H+} + +// NewView - a new OS/2 Help Viewer +// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) +// This software is released under the Gnu Public License - see readme.txt + +Interface + +// Defines settings (options) in a record and contains functions +// for loading and saving them to ini file. + +Uses + Classes + ,contnrs + ,fpg_base + ,fpg_main + ,CanvasFontManager + ; + +Const + ContentsBackgroundColorIndex = 0; + ContentsTextColorIndex = 1; + ContentsLinesColorIndex = 2; + IndexBackgroundColorIndex = 3; + IndexTextColorIndex = 4; + SearchBackgroundColorIndex = 5; + SearchTextColorIndex = 6; + NotesListBackgroundColorIndex = 7; + NotesListTextColorIndex = 8; + TopicBackgroundColorIndex = 9; + NotesTextColorIndex = 10; + SearchHighlightTextColorIndex = 11; + NumColorSettings = 12; + + // already defined, but these values are slightly different + //clLightYellow = $ffffc0; + //clLightBlue = $e0e0ff; + //clLightCyan = $c0ffff; + //clLightGreen = $e0ffe0; + + VGADefaultColors: array[ 0 .. NumColorSettings - 1 ] of TfpgColor + = ( clBoxColor, + clText1, + clText1, + clBoxColor, + clText1, + clBoxColor, + clText1, + clBoxColor, + clText1, + clBoxColor, + clGreen, + clYellow ); + + DefaultColors: array[ 0 .. NumColorSettings - 1 ] of TfpgColor + = ( clLightCyan, + clBlack, + clBlue, + clLightGreen, + clBlack, + clLightBlue, + clBlack, + clWhite, + clBlack, + clWhite, + clGreen, + clYellow ); + + ApplicationFontIndex = 0; + NumFontSettings = 1; + + DefaultTopicFont = DefaultTopicFont + '-' + DefaultTopicFontSize; + DefaultTopicFixedFont = DefaultTopicFixedFont + '-10' + DefaultTopicFixedFontSize; + + +Type + TIndexStyle = ( isAlphabetical, isFileOnly, isFull ); + TToolbarStyle = ( tsNone, tsImages, tsText, tsImagesAndText ); + TGlobalSearchLocation = ( gsHelpPaths, gsFixedDrives, gsSelectedHelpPaths, gsCustom ); + + TMRUItem = class(TObject) + public + Title: string; + Filenames: TStringList; + constructor Create; + destructor Destroy; override; + end; + + + TSettings = record + MRUList: TObjectList; + LastOpenDirectory: string; + LastSaveDirectory: string; + StartupHelp: boolean; + LeftPanelWidth: longint; + ShowLeftPanel_Help: boolean; + ShowLeftPanel_Standalone: boolean; + FileDialogSplit: Double; + Colors: array[ 0..NumColorSettings - 1 ] of TfpgColor; + NormalFont: TfpgFont; + FixedFont: TfpgFont; + Fonts: array[ 0..NumFontSettings - 1 ] of TfpgFont; + FixedFontSubstitution: boolean; + FixedFontSubstitutes: string; + IndexStyle: TIndexStyle; + SmoothScrolling: boolean; + UseOriginalDialogs: boolean; + OpenWithExpandedContents: boolean; + ToolbarBackgroundImageFilename: string; + ToolbarStyle: TToolbarStyle; + ConfirmWinHelp: boolean; + GlobalSearchLocation: TGlobalSearchLocation; + SearchDirectories: TStringList; + IPFTopicSaveAsEscaped: boolean; + end; + +// global procs +procedure LoadSettings; +procedure SaveSettings; +procedure writeSettingsDetailsTo(aStrings : TStrings); +procedure AddToMRUList( const Title: string; Filenames: TStrings ); + +var + Settings: TSettings; + +Implementation + +Uses + SysUtils + ,fpg_iniutils + ,ACLStringUtility + ,nvUtilities + ; + +const + GeneralSection = 'General'; + FontsSection = 'Fonts'; + ColoursSection = 'Colours'; + MRUSection = 'RecentFiles'; + MRUItemBaseSection = 'RecentFile'; + SearchSection = 'Search'; + + DefaultWidth = 620; + DefaultHeight = 460; + + MaxMRUListEntries = 9; + +constructor TMRUItem.Create; +begin + Title := ''; + Filenames := TStringList.Create; +end; + +destructor TMRUItem.Destroy; +begin + Filenames.Free; + inherited Destroy; +end; + +Procedure LoadSettings; +var + ColorIndex: longint; + DefaultColor: TfpgColor; + FontName: string; + SettingString: string; + MRUItem: TMRUItem; + MRUItemSection: string; + MRUItemFileCount: longint; + MRUItemFileIndex: longint; + i: longint; + Count: longint; + MRUFilename: string; +// MRUFileTitle: string; +begin + LogEvent(LogSettings, 'LoadSettings' ); + with gINI do + begin + EraseSection( 'Windows' ); + with Settings do + begin + LastOpenDirectory := ReadString( GeneralSection, 'LastOpenDirectory', '' ); + LastSaveDirectory := ReadString( GeneralSection, 'LastSaveDirectory', '' ); + + // Read split points, as units of 0.1% + LeftPanelWidth := ReadInteger( GeneralSection, 'LeftPanelWidth', 225 ); + FileDialogSplit := ReadInteger( GeneralSection, 'FileDialogSplit', 500 ) / 1000; + + ShowLeftPanel_Help := ReadBool( GeneralSection, 'ShowLeftPanel_Help', true ); + ShowLeftPanel_Standalone := ReadBool( GeneralSection, 'ShowLeftPanel_Standalone', true ); + + // Colours + for ColorIndex := 0 to High( Colors ) do + begin + //if GetScreenColorDepth > 8 then + DefaultColor := DefaultColors[ ColorIndex ]; + //else + // DefaultColor := VGADefaultColors[ ColorIndex ]; + Colors[ ColorIndex ] := ReadInteger( ColoursSection, + 'Color' + IntToStr( ColorIndex ), + DefaultColor ); + end; + + // Most Recently Used files list... + Count := ReadInteger( MRUSection, 'Count', 0 ); + for i := 0 to Count - 1 do + begin + MRUItemSection := MRUItemBaseSection + IntToStr( i ); + MRUItem := TMRUItem.Create; + MRUItem.Title := ReadString( MRUItemSection, 'Title', '' ); + MRUItemFileCount := ReadInteger( MRUItemSection, 'FileCount', 0 ); + for MRUItemFileIndex := 0 to MRUItemFileCount - 1 do + begin + MRUFilename := ReadString( MRUItemSection, + 'File' + IntToStr( MRUItemFileIndex ), + '' ); + if MRUFilename <> '' then + MRUItem.Filenames.Add( MRUFilename ); + end; + if ( MRUItem.Title <> '' ) and ( MRUItem.Filenames.Count > 0 ) then + // valid MRU item + MRUList.Add( MRUItem ) + else + begin + // not valid + MRUItem.Free; + MRUItem := nil; + end; + end; + + // Fonts + NormalFont := fpgGetFont(ReadString(FontsSection, 'NormalFont', DefaultTopicFont)); + if NormalFont = nil then + NormalFont := fpgStyle.DefaultFont; + + FixedFont := fpgGetFont(ReadString(FontsSection, 'FixedFont', DefaultTopicFixedFont)); + if FixedFont = nil then + FixedFont := fpgStyle.FixedFont; + + for i := 0 to NumFontSettings - 1 do + begin + FontName := 'Font' + IntToStr( i ); + Fonts[ i ] := nil; + if ReadBool( FontsSection, FontName + 'Customised', false ) then + Fonts[ i ] := fpgGetFont(ReadString(FontsSection, FontName + 'Desc', DefaultTopicFont)); + end; + + FixedFontSubstitution := ReadBool( FontsSection, 'FixedFontSubstitution', true ); + FixedFontSubstitutes := ReadString( FontsSection, 'FixedFontSubstitutes', 'Mono-10' ); + + // Index style + SettingString := ReadString( GeneralSection, 'IndexStyle', 'Full' ); + if SameText( SettingString, 'FileOnly' ) then + IndexStyle := isFileOnly + else if Sametext( SettingString, 'Alphabetical' ) then + IndexStyle := isAlphabetical + else + IndexStyle := isFull; + + StartupHelp := ReadBool( GeneralSection, 'StartupHelp', true ); + + SmoothScrolling := ReadBool( GeneralSection, 'SmoothScrolling', true ); +// UseOriginalDialogs := ReadBool( GeneralSection, 'UseOriginalDialogs', false ); + OpenWithExpandedContents := ReadBool( GeneralSection, 'OpenWithExpandedContents', false ); + +// ToolBarBackgroundImageFilename := ReadString( GeneralSection, 'ToolbarBackground', '' ); + SettingString := ReadString( GeneralSection, 'ToolbarStyle', 'ImagesAndText' ); + + if SameText( SettingString, 'None' ) then + ToolbarStyle := tsNone + else if SameText( SettingString, 'Images' ) then + ToolbarStyle := tsImages + else if SameText( SettingString, 'Text' ) then + ToolbarStyle := tsText + else + ToolbarStyle := tsImagesAndText; + + ConfirmWinHelp := ReadBool( GeneralSection, 'ConfirmWinHelp', true ); + IPFTopicSaveAsEscaped := ReadBool(GeneralSection, 'IPFTopicSaveAsEscaped', true); + + Count := ReadInteger( SearchSection, 'CustomDirCount', 0 ); + + SearchDirectories.Clear; + for i := 0 to Count - 1 do + begin + SettingString := ReadString( SearchSection, + 'CustomDir' + IntToStr( i ), + '' ); + if trim( SettingString ) <> '' then + SearchDirectories.Add( SettingString ); + end; + SettingString := ReadString( SearchSection, + 'Location', + 'HelpPaths' ); + if SameText( SettingString, 'HelpPaths' ) then + GlobalSearchLocation := gsHelpPaths + else if SameText( SettingString, 'FixedDrives' ) then + GlobalSearchLocation := gsFixedDrives + else if SameText( SettingString, 'SelectedHelpPaths' ) then + GlobalSearchLocation := gsSelectedHelpPaths + else + GlobalSearchLocation := gsCustom; + + end; + end; + LogEvent(LogSettings, ' Done' ); +end; + +procedure SaveSettings; +var + ColorIndex: longint; + FontIndex: longint; + FontName: string; + i: longint; + MRUItemFileIndex: longint; + SettingString: string; + MRUItem: TMRUItem; + MRUItemSection: string; +begin + LogEvent(LogSettings, 'SaveSettings' ); + with gINI do + begin + with Settings do + begin + WriteString( GeneralSection, 'LastOpenDirectory', LastOpenDirectory ); + WriteString( GeneralSection, 'LastSaveDirectory', LastSaveDirectory ); + + WriteInteger( GeneralSection, 'LeftPanelWidth', LeftPanelWidth ); + // Write split points, as units of 0.1% + WriteInteger( GeneralSection, 'FileDialogSplit', Round( FileDialogSplit * 1000 ) ); + + WriteBool( GeneralSection, 'ShowLeftPanel_Help', ShowLeftPanel_Help ); + WriteBool( GeneralSection, 'ShowLeftPanel_Standalone', ShowLeftPanel_Standalone ); + + // Colours + for ColorIndex := 0 to High( Colors ) do + WriteInteger( ColoursSection, + 'Color' + IntToStr( ColorIndex ), + Colors[ ColorIndex ] ); + + // MRU files + WriteInteger( MRUSection, 'Count', MRUList.Count ); + for i := 0 to MRUList.Count - 1 do + begin + MRUItem := TMRUItem(MRUList[ i ]); + MRUItemSection := MRUItemBaseSection + IntToStr( i ); + EraseSection( MRUItemSection ); + WriteString( MRUItemSection, 'Title', MRUItem.Title ); + WriteInteger( MRUItemSection, 'FileCount', MRUItem.Filenames.Count ); + for MRUItemFileIndex := 0 to MRUItem.Filenames.Count - 1 do + WriteString( MRUItemSection, + 'File' + IntToStr( MRUItemFileIndex ), + MRUItem.Filenames[ MRUItemFileIndex ] ); + end; + + // clear unused sections + for i := MRUList.Count to MaxMRUListEntries do + begin + MRUItemSection := MRUItemBaseSection + IntToStr( i ); + EraseSection( MRUItemSection ); + end; + + // Fonts + WriteString( FontsSection, 'NormalFont', NormalFont.FontDesc ); + WriteString( FontsSection, 'FixedFont', FixedFont.FontDesc ); + for FontIndex := 0 to NumFontSettings - 1 do + begin + FontName := 'Font' + IntToStr( FontIndex ); + WriteBool( FontsSection, FontName + 'Customised', Fonts[ FontIndex ] <> nil ); + if Fonts[ FontIndex ] <> nil then + WriteString( FontsSection, FontName + 'Desc', Fonts[ FontIndex ].FontDesc ); + end; + + WriteBool( FontsSection, 'FixedFontSubstitution', FixedFontSubstitution ); + WriteString( FontsSection, 'FixedFontSubstitutes', FixedFontSubstitutes ); + + case IndexStyle of + isFileOnly: + SettingString := 'FileOnly'; + isAlphabetical: + SettingString := 'Alphabetical'; + isFull: + SettingString := 'Full'; + end; + WriteString( GeneralSection, 'IndexStyle', SettingString ); + + WriteBool( GeneralSection, 'StartupHelp', StartupHelp ); + WriteBool( GeneralSection, 'SmoothScrolling', SmoothScrolling ); +// WriteBool( GeneralSection, 'UseOriginalDialogs', UseOriginalDialogs ); + WriteBool( GeneralSection, 'OpenWithExpandedContents', OpenWithExpandedContents ); +// WriteString( GeneralSection, 'ToolbarBackground', ToolbarBackgroundImageFilename ); + + case ToolbarStyle of + tsNone: + SettingString := 'None'; + tsImages: + SettingString := 'Images'; + tsText: + SettingString := 'Text'; + tsImagesAndText: + SettingString := 'ImagesAndText'; + end; + + WriteString( GeneralSection, 'ToolbarStyle', SettingString ); + WriteBool( GeneralSection, 'ConfirmWinHelp', ConfirmWinHelp ); + WriteBool( GeneralSection, 'IPFTopicSaveAsEscaped', IPFTopicSaveAsEscaped); + WriteInteger( SearchSection, 'CustomDirCount', SearchDirectories.Count ); + + SearchDirectories.Sorted := true; + SearchDirectories.CaseSensitive := True; + SearchDirectories.Duplicates := dupIgnore; + + for i := 0 to SearchDirectories.Count - 1 do + begin + WriteString( SearchSection, + 'CustomDir' + IntToStr( i ), + SearchDirectories[ i ] ); + end; + + case GlobalSearchLocation of + gsHelpPaths: + SettingString := 'HelpPaths'; + + gsFixedDrives: + SettingString := 'FixedDrives'; + + gsSelectedHelpPaths: + SettingString := 'SelectedHelpPaths'; + + gsCustom: + SettingString := 'Custom'; + end; + + WriteString( SearchSection, 'Location', SettingString ); + end; + end; + LogEvent(LogSettings, ' Done' ); +End; + +Procedure AddToMRUList( const Title: string; Filenames: TStrings ); +var + MRUIndex: longint; + PreviousMRUIndex: longint; + MRUItem: TMRUItem; +begin + PreviousMRUIndex := -1; + for MRUIndex := 0 to Settings.MRUList.Count - 1 do + begin + MRUItem := TMRUItem(Settings.MRUList[ MRUIndex ]); + if ( MRUItem.Title = Title ) + and ( MRUItem.Filenames.Equals( Filenames ) ) then + begin + // found identical entry in the list already. + PreviousMRUIndex := MRUIndex; + break; + end; + end; + + if PreviousMRUIndex > -1 then + begin + // is already in list. Get instance so we can move it to the top of list + MRUItem := TMRUItem(Settings.MRUList[PreviousMRUIndex]); + Settings.MRUList.Extract(MRUItem); + end + else + begin + // not yet in list. Create new + MRUItem := TMRUItem.Create; + MRUItem.Title := Title; + MRUItem.Filenames.Assign( Filenames ); + end; + + Settings.MRUList.Insert( 0, MRUItem ); + while Settings.MRUList.Count > MaxMRUListEntries do + begin + Settings.MRUList.Remove(Settings.MRUList.Last); + end; +end; + +procedure writeSettingsDetailsTo(aStrings : TStrings); +Begin + aStrings.Add(''); + aStrings.Add('---- Settings ----'); + aStrings.Add('info: Screenwidth=' + IntToStr(fpgApplication.ScreenWidth)); + aStrings.Add('info: Screenheight=' + IntToStr(fpgApplication.ScreenHeight)); + aStrings.Add('info: dpi=' + IntToStr(fpgApplication.Screen_dpi)); + + aStrings.Add('LastOpenDirectory: ' + Settings.LastOpenDirectory); + aStrings.Add('LastSaveDirectory: ' + Settings.LastSaveDirectory); + aStrings.Add('StartupHelp: ' + boolToStr(Settings.StartupHelp)); + // LeftPanelWidth: longint; + aStrings.Add('ShowLeftPanel_Help: ' + boolToStr(Settings.ShowLeftPanel_Help)); + aStrings.Add('ShowLeftPanel_Standalone: ' + boolToStr(Settings.ShowLeftPanel_Standalone)); + // FileDialogSplit: real; + // Colors: array[ 0..NumColorSettings - 1 ] of TColor; + // NormalFont: TFont; + // FixedFont: TFont; + // Fonts: array[ 0..NumFontSettings - 1 ] of TFont; + aStrings.Add('FixedFontSubstitution: ' + boolToStr(Settings.FixedFontSubstitution)); + aStrings.Add('FixedFontSubstitutes: ' + Settings.FixedFontSubstitutes); + // IndexStyle: TIndexStyle; + aStrings.Add('SmoothScrolling: ' + boolToStr(Settings.SmoothScrolling)); +// aStrings.Add('UseOriginalDialogs: ' + boolToStr(Settings.UseOriginalDialogs)); + aStrings.Add('OpenWithExpandedContents: ' + boolToStr(Settings.OpenWithExpandedContents)); +// aStrings.Add('ToolbarBackgroundImageFilename: ' + Settings.ToolbarBackgroundImageFilename); + // ToolbarStyle: TToolbarStyle; + aStrings.Add('ConfirmWinHelp: ' + boolToStr(Settings.ConfirmWinHelp)); + aStrings.Add('IPFTopicSaveAsEscaped: ' + BoolToStr(Settings.IPFTopicSaveAsEscaped)); + // GlobalSearchLocation: TGlobalSearchLocation; + // SearchDirectories: TStringList; +end; + + +Initialization + Settings.MRUList := TObjectList.Create; + + //Settings.NormalFont := fpgStyle.DefaultFont; + //Settings.FixedFont := fpgStyle.FixedFont; + //Settings.SearchDirectories := TStringList.Create; + +Finalization + Settings.NormalFont.Free; + Settings.FixedFont.Free; + Settings.SearchDirectories.Free; + Settings.MRUList.Free; + +End. diff --git a/docview/src/TextSearchQuery.pas b/docview/src/TextSearchQuery.pas new file mode 100644 index 00000000..3a8037a2 --- /dev/null +++ b/docview/src/TextSearchQuery.pas @@ -0,0 +1,208 @@ +Unit TextSearchQuery; + +{$mode objfpc}{$H+} + +// NewView - a new OS/2 Help Viewer +// Copyright 2003 Aaron Lawrence (aaronl at consultant dot com) +// This software is released under the Gnu Public License - see readme.txt + +Interface + +// Encapsulates a parsed search query. + +uses + Classes, SysUtils; + +Type + ESearchSyntaxError = class( Exception ) + end; + + TSearchTermCombineMethod = + ( + cmOptional, + cmRequired, + cmExcluded + ); + + TSearchTerm = class(TObject) + public + Text: string; + Parts: TStringList; + CombineMethod: TSearchTermCombineMethod; + constructor Create( const TheText: string; + const TheCombineMethod: TSearchTermCombineMethod ); + destructor Destroy; override; + end; + + + TTextSearchQuery = class(TObject) + protected + Terms: TList; + function GetTerm( Index: longint ): TSearchTerm; + function GetTermCount: longint; + public + constructor Create( const SearchString: string ); + destructor Destroy; override; + property Term[ Index: longint ]: TSearchTerm read GetTerm; + property TermCount: longint read GetTermCount; + end; + + +implementation + +uses + nvUtilities + ,ACLStringUtility + ; + + +const + QueryErrorMissingWord1 = 'No search word given after'; + QueryErrorMissingWord2 = ' before '; + + +constructor TTextSearchQuery.Create( const SearchString: string ); +var + TermText: string; + CombineMethod: TSearchTermCombineMethod; + lTerm: TSearchTerm; + tmpTerms : TStrings; + i : integer; +begin + inherited Create; + Terms := TList.Create; + try + tmpTerms := TStringList.Create; + StrExtractStringsQuoted(tmpTerms, SearchString); + + for i := 0 to tmpTerms.count-1 do + begin + TermText := tmpTerms[i]; + + // Check for modifiers: + // + word must be matched + // - word must not be matched + case TermText[ 1 ] of + '+': + CombineMethod := cmRequired; + '-': + CombineMethod := cmExcluded; + else + CombineMethod := cmOptional; + end; + + if CombineMethod <> cmOptional then + begin + // delete + or - + if Length( TermText ) = 1 then + if (i < tmpTerms.count-1) then + raise ESearchSyntaxError.Create( QueryErrorMissingWord1 + + StrInDoubleQuotes(TermText) + + QueryErrorMissingWord2 + + StrInDoubleQuotes(tmpTerms[i+1]) ) + else + raise ESearchSyntaxError.Create( QueryErrorMissingWord1 + + StrInDoubleQuotes(TermText)); + Delete( TermText, 1, 1 ); + end; + + lTerm := TSearchTerm.Create( TermText, + CombineMethod ); + Terms.Add( lTerm ); + end; + tmpTerms.Free; + except + while Terms.Count > 0 do + begin + lTerm := TSearchTerm(Terms.Last); + Terms.Remove(lTerm); + lTerm.Free; + end; + Terms.Free; + raise; // reraise exception + end; +end; + +destructor TTextSearchQuery.Destroy; +begin + DestroyListObjects( Terms ); + Terms.Free; + inherited Destroy; +end; + +function TTextSearchQuery.GetTerm( index: longint ): TSearchTerm; +begin + Result := TSearchTerm(Terms[ Index ]); +end; + +function TTextSearchQuery.GetTermCount: longint; +begin + Result := Terms.Count; +end; + +constructor TSearchTerm.Create( const TheText: string; + const TheCombineMethod: TSearchTermCombineMethod ); +var + TermParseIndex: longint; + TermChar: char; + TermPart: string; +begin + Parts := TStringList.Create; + + Text := TheText; + CombineMethod := TheCombineMethod; + + // Break out each part of the term as IPF does: + // consecutive alphanumeric chars become a "word" + // but each symbol is a separate word, and symbols break + // up alphanumerics into multiple words. e.g. + // CAKE_SAUSAGE becomes three words in IPF, + // one each for "CAKE" "_" and "SAUSAGE" + + TermParseIndex := 1; + while TermParseIndex <= Length( Text ) do + begin + // collect alphanumeric chars + TermPart := ''; + while TermParseIndex <= Length( Text ) do + begin + TermChar := Text[ TermParseIndex ]; + if ( IsAlpha( TermChar ) + or IsDigit( TermChar ) ) then + begin + // alpha numeric, collect it + TermPart := TermPart + TermChar; + inc( TermParseIndex ); + end + else + begin + // not alpha numeric, so stop + break; + end; + end; + if Length( TermPart ) > 0 then + begin + Parts.Add( TermPart ); // add collected alphanumeric part + end; + + if TermParseIndex <= Length( Text ) then + begin + // must be a symbol, + // each symbol (excluding space) is an individual item + if Text[ TermParseIndex ] <> ' ' then + Parts.Add( Text[ TermParseIndex ] ); + inc( TermParseIndex ); + end; + + end; + +end; + +destructor TSearchTerm.Destroy; +begin + Parts.Free; + inherited Destroy; +end; + + +end. diff --git a/docview/src/arrows.inc b/docview/src/arrows.inc new file mode 100644 index 00000000..bfe510a5 --- /dev/null +++ b/docview/src/arrows.inc @@ -0,0 +1,380 @@ + +Const + usr_arrow_up : Array[0..1549] of byte = ( + 66, 77, 14, 6, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 22, 0, 0, 0, 22, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 216, 5, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 4, + 115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,169,227,197,133,218,175,131,219,174,131, + 222,176,132,223,177,132,224,177,132,223,177,131,221,175, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,169,228,198, 30,189,108, 16,190,102, 18, + 196,106, 19,201,109, 20,203,110, 19,199,108,132,223,177, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,169,228,198, 41,193,116, 16,191,102, 18, + 198,107, 20,204,111, 21,208,113, 19,202,109,132,224,177, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,166,227,196, 56,195,124, 16,189,101, 17, + 195,105, 18,199,107, 19,200,108, 18,198,107,132,223,176, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,165,225,194, 74,199,135, 20,186,102, 16, + 189,101, 16,191,103, 17,192,103, 16,191,102,131,220,175, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 4,117, 59, 4,115, 58, 4,115, + 58, 4,115, 58, 5,120, 61,162,222,192, 69,193,130, 42,187,114, 14, + 182, 96, 14,184, 97, 14,184, 98, 14,183, 97,130,217,172, 5,121, 62, + 4,115, 58, 4,115, 58, 4,115, 58, 4,118, 58,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 3,114, 57, 40,138, 88,160,215, + 187,165,219,191,163,219,190,160,220,189, 65,187,125, 58,188,121, 25, + 179,101, 12,176, 92, 12,176, 92, 12,175, 92,129,213,170,128,211,169, + 128,209,168,121,203,160, 31,133, 79, 3,114, 59,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58, 55,148, + 100,152,213,181, 81,184,132, 65,181,121, 59,181,119, 52,180,115, 44, + 179,110, 17,170, 92, 10,168, 87, 9,167, 87, 9,166, 86, 23,168, 94, + 114,200,156, 41,142, 91, 4,117, 57,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3,118, + 60, 73,161,116,141,209,173, 64,177,119, 52,173,111, 45,172,107, 39, + 171,104, 28,168, 97, 9,161, 83, 7,160, 81, 14,161, 86,106,195,149, + 57,154,104, 3,117, 60,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0, 85, + 85, 4,118, 58, 89,172,130,125,202,162, 49,172,108, 40,168,102, 33, + 165, 97, 26,162, 92, 15,158, 84, 8,155, 79, 95,190,141, 72,167,118, + 4,118, 59, 0,102, 51,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0,118, 59, 3,118, 60,101,182,140,105,194,148, 35,166, 99, 28, + 163, 94, 22,160, 89, 14,157, 84, 80,184,131, 85,178,130, 3,117, 59, + 0,113, 57,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0,114, 62, 9,118, 62,112,191,150, 82,185,133, 24, + 161, 90, 17,158, 86, 67,179,121,100,187,142, 8,120, 61, 7,118, 59, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255, 5,113, 59, 16,124, 69,118,195,156, 58, + 175,114, 53,173,111,109,193,150, 15,123, 68, 4,116, 56,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255, 3,117, 58, 24,129, 75,117, + 197,156,114,197,154, 23,131, 75, 5,117, 60,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,118, 59, 34, + 139, 85, 34,141, 85, 3,117, 60,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3, + 118, 60, 4,118, 60, 0, 85, 85,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0); + +Const + usr_arrow_down : Array[0..1549] of byte = ( + 66, 77, 14, 6, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 22, 0, 0, 0, 22, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 216, 5, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3, + 109, 54, 5,112, 57,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3,110, 57, 35, + 142, 87, 36,149, 90, 3,113, 57,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255, 3,117, 58, 22,130, 76,119, + 216,167,120,221,170, 24,137, 79, 5,117, 60,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255, 5,113, 59, 15,123, 68,114,209,161, 57, + 206,131, 59,211,134,117,221,169, 15,126, 69, 4,116, 56,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0,114, 62, 9,119, 63,102,198,149, 72,204,137, 17, + 195,105, 19,200,108, 74,214,143,105,208,157, 9,122, 63, 7,118, 59, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0,118, 59, 5,119, 60, 94,186,138, 85,201,142, 14,185, 98, 16, + 189,101, 17,193,103, 17,194,104, 87,211,148, 89,193,139, 4,118, 60, + 0,113, 57,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 0, 85, + 85, 4,118, 58, 89,175,131,129,211,169, 19,177, 96, 13,179, 94, 14, + 183, 97, 14,185, 98, 15,186, 99, 15,185, 99,100,208,153, 74,175,123, + 4,119, 60, 0,102, 51,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3,118, + 60, 73,160,116,144,212,177, 71,186,128, 52,183,116, 15,173, 93, 12, + 175, 92, 12,177, 93, 12,178, 94, 12,177, 93, 18,177, 96,108,204,156, + 57,157,105, 3,118, 60,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58, 55,149, + 100,154,213,182, 86,186,134, 70,182,125, 63,182,121, 51,180,114, 24, + 173, 96, 10,169, 88, 10,170, 88, 10,169, 88, 10,168, 87, 23,170, 95, + 114,201,156, 40,142, 91, 4,117, 57,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 3,114, 57, 41,138, 88,163,216, + 189,168,219,193,164,218,190,160,216,187, 65,178,121, 58,178,116, 49, + 176,111, 34,170,101, 9,162, 84, 8,161, 83,126,205,165,126,204,164, + 126,203,163,119,198,158, 29,131, 79, 3,114, 59,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 4,117, 59, 4,115, 58, 4,115, + 58, 4,115, 58, 5,119, 60,162,217,189, 69,179,123, 60,176,116, 50, + 172,109, 42,168,103, 26,162, 92, 6,154, 78,126,202,163, 5,119, 60, + 4,115, 58, 4,115, 58, 4,115, 58, 4,117, 58,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,164,218,190, 71,180,124, 62,176,118, 52, + 173,111, 44,169,105, 35,165, 98, 17,158, 86,126,202,163, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,165,218,191, 73,181,125, 64,177,119, 54, + 173,112, 45,169,105, 36,166, 99, 24,161, 91,126,202,163, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,166,218,191, 74,181,126, 64,177,119, 54, + 173,112, 46,170,106, 37,166,100, 26,162, 92,127,203,164, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,166,218,191, 74,181,126, 64,177,119, 54, + 173,112, 46,170,106, 36,166, 99, 26,162, 92,127,203,164, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,115, 58,165,218,191,160,216,187,156,214,184,150, + 212,180,146,210,177,141,208,174,136,206,170,128,203,164, 4,115, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 4,117, 58, 4,115, 58, 4,115, 58, 4,115, 58, 4, + 115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 4,117, 58, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0); + +Const + usr_arrow_left : Array[0..1549] of byte = ( + 66, 77, 14, 6, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 22, 0, 0, 0, 22, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 216, 5, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255, 0,102, 51, 3,110, 55,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3, + 89, 45, 27,129, 76, 6,116, 60,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 2,101, 51, 37, + 139, 87,121,206,163, 8,118, 60,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255, 3,110, 55, 54,152,102,117, + 208,161,129,216,172, 8,118, 60,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0,102, 51, 3,114, 58, 69,166,117,111,206,157, 28, + 185,105,128,219,173, 9,125, 65, 4,115, 58, 4,115, 58, 4,115, 58, + 4,115, 58, 4,115, 58, 4,115, 59,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 3, 88, 44, 7,118, 60, 83,176,129, 99,198,148, 20,177, 97, 14, + 182, 96,128,221,174,132,225,178,134,230,181,133,229,180,132,225,178, + 131,221,175,130,217,173, 8,118, 60, 0,102, 51,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,109, + 55, 13,121, 66, 97,185,140, 83,187,134, 12,165, 87, 11,172, 90, 13, + 180, 95, 15,187,100, 17,193,104, 18,198,107, 18,197,106, 17,193,103, + 15,186, 99,127,215,170, 8,117, 60, 0,102, 51,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 0,128, 0, 4,116, 58, 21,130, + 73,108,192,149, 68,179,122, 6,154, 78, 7,160, 82, 9,167, 87, 11, + 174, 91, 13,180, 95, 14,184, 98, 15,187,100, 15,187, 99, 14,184, 98, + 13,179, 95,126,212,168, 8,117, 61, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 3,119, 59, 32,138, 84,113,196, + 154, 54,173,112, 13,157, 83, 16,158, 85, 15,158, 84, 15,164, 87, 11, + 167, 87, 10,171, 89, 11,174, 91, 12,176, 92, 12,176, 92, 11,174, 91, + 11,170, 90,132,211,170, 8,117, 61, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255, 3,117, 59, 32,136, 84,116,196, + 155, 62,177,117, 23,161, 90, 28,163, 94, 33,165, 97, 37,167,100, 43, + 171,105, 44,175,107, 40,176,106, 35,175,103, 35,175,103, 41,175,107, + 54,178,114,149,213,180, 8,117, 61, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,116, 58, 22,128, + 73,116,195,154, 85,186,135, 35,166, 99, 40,168,102, 45,170,106, 50, + 172,109, 54,173,112, 58,175,115, 61,176,117, 63,177,118, 65,178,120, + 65,178,120,154,214,184, 9,118, 62, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3,114, + 57, 15,123, 68,108,188,148,107,195,150, 49,172,109, 52,173,111, 57, + 175,114, 62,177,118, 66,178,120, 68,179,122, 71,181,125, 73,181,126, + 74,182,127,159,216,187, 10,118, 63, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 5,117, 58, 8,119, 61, 99,182,139,127,203,164, 66,178,120, 65, + 178,120,157,215,185,161,217,188,163,218,190,164,218,191,166,219,192, + 166,219,192,166,219,192, 10,118, 63, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255, 0,118, 59, 4,116, 60, 85,170,126,142,209,175, 82, + 185,132,160,217,188, 11,123, 65, 4,115, 58, 4,115, 58, 4,115, 58, + 4,115, 58, 4,115, 58, 4,117, 59, 0,128, 64,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255, 0,116, 70, 4,117, 59, 69,158,113,152, + 212,181,165,218,191, 11,119, 63, 0,128, 64,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255, 0,128, 0, 3,116, 58, 52, + 146, 98,159,214,186, 11,119, 63, 0,128, 64,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 5, + 117, 60, 37,136, 84, 7,117, 60, 0,128, 64,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255, 4,115, 57, 4,116, 58, 0,128, 64,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0); + +Const + usr_arrow_right : Array[0..1549] of byte = ( + 66, 77, 14, 6, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 22, 0, 0, 0, 22, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 216, 5, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 3,110, 55, 2, + 82, 42,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,115, 58, 30, + 131, 78, 3, 93, 47,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,122, + 207,164, 41,141, 90, 2,102, 51,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,130, + 216,172,118,214,166, 58,159,107, 4,112, 56,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 57, 4,115, + 58, 4,115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 5,119, 61,131, + 219,174, 31,198,113,114,222,167, 74,180,127, 4,116, 59,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,126,203, + 163,126,203,163,126,203,164,127,207,166,129,212,169,130,216,172,131, + 220,175, 17,195,105, 26,204,115,105,223,163, 90,195,142, 9,119, 62, + 2, 86, 44,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,126,203, + 163, 6,154, 78, 6,154, 78, 8,161, 82, 10,168, 87, 12,176, 92, 14, + 183, 97, 16,189,101, 17,193,103, 19,194,105, 89,211,149,103,201,152, + 15,125, 68, 4,110, 56,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,126,203, + 163, 6,154, 78, 6,154, 78, 6,156, 79, 8,163, 84, 10,169, 88, 12, + 175, 92, 13,179, 94, 14,182, 96, 14,182, 96, 13,180, 95, 71,196,132, + 112,202,156, 22,131, 74, 3,119, 59, 0,128, 0,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,133,205, + 168, 7,154, 79, 6,154, 78, 6,154, 78, 6,156, 79, 8,161, 82, 9, + 165, 85, 12,170, 88, 18,174, 94, 21,175, 96, 22,173, 97, 19,170, 93, + 58,182,119,117,199,157, 34,139, 85, 4,118, 59,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,151,213, + 181, 52,173,111, 40,168,102, 33,165, 97, 33,165, 97, 39,167,102, 45, + 171,107, 46,173,107, 43,173,106, 39,171,104, 34,169,100, 28,165, 95, + 65,178,120,120,198,159, 34,138, 86, 3,117, 59,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,156,215, + 185, 65,178,120, 65,178,120, 65,178,120, 63,177,118, 61,176,117, 58, + 175,115, 54,173,112, 50,172,109, 45,170,106, 40,168,102, 88,188,137, + 120,197,157, 24,129, 75, 4,115, 58,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,160,217, + 188, 74,182,127, 74,182,127, 73,181,126, 71,181,125, 68,179,122, 66, + 178,120, 62,177,118, 58,175,115, 54,174,112,111,197,152,113,191,151, + 16,124, 69, 3,116, 60,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,166,219, + 192,166,219,192,166,219,192,166,219,192,164,218,191,163,218,190,161, + 217,188, 69,180,123, 71,180,124,130,204,166,103,183,141, 9,118, 61, + 5,114, 57,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255, 4,117, 58, 4,115, + 58, 4,115, 58, 4,115, 58, 4,115, 58, 4,115, 58, 5,119, 60,165, + 218,191, 87,187,135,144,210,176, 91,173,131, 3,116, 59, 0,118, 55, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,169, + 220,193,155,213,183, 74,161,116, 4,116, 59, 0,106, 64,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,115, 58,164, + 217,190, 57,149,102, 3,118, 60, 0, 85, 85,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,115, 58, 41, + 137, 88, 5,117, 59,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, 4,117, 59, 3, + 114, 57,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, + 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255, + 255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0,255,255, 0, + 255, 0, 0); diff --git a/docview/src/docdump/docdump.lpi b/docview/src/docdump/docdump.lpi new file mode 100644 index 00000000..440c482c --- /dev/null +++ b/docview/src/docdump/docdump.lpi @@ -0,0 +1,114 @@ +<?xml version="1.0"?> +<CONFIG> + <ProjectOptions> + <Version Value="7"/> + <General> + <SessionStorage Value="InIDEConfig"/> + <MainUnit Value="0"/> + <TargetFileExt Value=""/> + <UseAppBundle Value="False"/> + </General> + <VersionInfo> + <ProjectVersion Value=""/> + </VersionInfo> + <PublishOptions> + <Version Value="2"/> + <IgnoreBinaries Value="False"/> + <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> + <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> + </local> + </RunParams> + <Units Count="15"> + <Unit0> + <Filename Value="docdump.lpr"/> + <IsPartOfProject Value="True"/> + <UnitName Value="docdump"/> + </Unit0> + <Unit1> + <Filename Value="../IPFEscapeCodes.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="IPFEscapeCodes"/> + </Unit1> + <Unit2> + <Filename Value="../IPFFileFormatUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="IPFFileFormatUnit"/> + </Unit2> + <Unit3> + <Filename Value="readheader.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readheader"/> + </Unit3> + <Unit4> + <Filename Value="filestreamhelper.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="filestreamhelper"/> + </Unit4> + <Unit5> + <Filename Value="readextfiles.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readextfiles"/> + </Unit5> + <Unit6> + <Filename Value="../../docs/inf04.txt"/> + <IsPartOfProject Value="True"/> + </Unit6> + <Unit7> + <Filename Value="readstrings.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readstrings"/> + </Unit7> + <Unit8> + <Filename Value="iterator_impl.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="iterator_impl"/> + </Unit8> + <Unit9> + <Filename Value="iterator_intf.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="iterator_intf"/> + </Unit9> + <Unit10> + <Filename Value="readnlsdata.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readnlsdata"/> + </Unit10> + <Unit11> + <Filename Value="readfonts.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readfonts"/> + </Unit11> + <Unit12> + <Filename Value="readcontrols.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readcontrols"/> + </Unit12> + <Unit13> + <Filename Value="readtoc.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="readtoc"/> + </Unit13> + <Unit14> + <Filename Value="u_Tools.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="u_Tools"/> + </Unit14> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="8"/> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)/"/> + <OtherUnitFiles Value="../"/> + <UnitOutputDirectory Value="units/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Other> + <CompilerPath Value="$(CompPath)"/> + </Other> + </CompilerOptions> +</CONFIG> diff --git a/docview/src/docdump/docdump.lpr b/docview/src/docdump/docdump.lpr new file mode 100644 index 00000000..84077608 --- /dev/null +++ b/docview/src/docdump/docdump.lpr @@ -0,0 +1,98 @@ +{ + Dumps the structure of an OS/2 IPF help file +} +program docdump; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX}{$IFDEF UseCThreads} + cthreads, + {$ENDIF}{$ENDIF} + Classes, SysUtils, IPFFileFormatUnit, IPFEscapeCodes, CustApp, readheader, + filestreamhelper, readextfiles, readstrings, iterator_intf, iterator_impl, + readnlsdata, readfonts, readcontrols, readtoc, u_Tools; + +type + + { TDocDump } + + TDocDump = class(TCustomApplication) + private + FIn: TFileStream; + FOut: TFileTextStream; + protected + procedure DoRun; override; + public + constructor Create(TheOwner: TComponent); override; + destructor Destroy; override; + procedure WriteHelp; virtual; + end; + +{ TDocDump } + +procedure TDocDump.DoRun; +var + ErrorMsg: String; +begin + // quick check parameters + ErrorMsg:=CheckOptions('h','help'); + if ErrorMsg<>'' then begin + ShowException(Exception.Create(ErrorMsg)); + Terminate; + Exit; + end; + + // parse parameters + if HasOption('h','help') then begin + WriteHelp; + Terminate; + Exit; + end; + + FIn := TFileStream.Create(ParamStr(1), fmOpenRead); + FOut := TFileTextStream.Create(ExtractFileName(ParamStr(1))+'.txt', fmCreate); + try + FOut.WriteLn(Format('File name: %s (%d bytes)', [ExtractFileName(ParamStr(1)), FIn.Size])); + ProcessHeader(FIn, FOut); + ProcessExtFiles(FIn, FOut); + ProcessStringsTable(FIn, FOut); + ProcessNLSData(FIn, FOut); + ProcessFonts(FIn, FOut); + ProcessControls(FIn, FOut); + ProcessTOC(FIn, FOut); + finally + FIn.Free; + FOut.Free; + end; + // stop program loop + Terminate; +end; + +constructor TDocDump.Create(TheOwner: TComponent); +begin + inherited Create(TheOwner); + StopOnException:=True; +end; + +destructor TDocDump.Destroy; +begin + inherited Destroy; +end; + +procedure TDocDump.WriteHelp; +begin + { add your help code here } + writeln('Usage: ',ExeName,' -h'); +end; + +var + Application: TDocDump; + + +begin + Application:=TDocDump.Create(nil); + Application.Run; + Application.Free; +end. + diff --git a/docview/src/docdump/filestreamhelper.pas b/docview/src/docdump/filestreamhelper.pas new file mode 100644 index 00000000..ff831a91 --- /dev/null +++ b/docview/src/docdump/filestreamhelper.pas @@ -0,0 +1,35 @@ +unit filestreamhelper; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +type + TFileTextStream = class(TFileStream) + public + procedure WriteLn(const fmt: String; const args: array of const); + procedure WriteLn(const s: String); + end; + +implementation + +{ TFileTextStream } + +procedure TFileTextStream.WriteLn(const fmt: String; const args: array of const); +var + temp: String; +begin + temp := Format(fmt, args) + LineEnding; + Write(temp[1], Length(temp)); +end; + +procedure TFileTextStream.WriteLn(const s: String); +begin + self.WriteLn('%s', [s]); +end; + +end. + diff --git a/docview/src/docdump/iterator_impl.pas b/docview/src/docdump/iterator_impl.pas new file mode 100644 index 00000000..4ad88052 --- /dev/null +++ b/docview/src/docdump/iterator_impl.pas @@ -0,0 +1,480 @@ +unit iterator_impl; + +{$mode objfpc}{$H+} + +interface + +uses + Classes + ,SysUtils + ,Regex { to be used with filtered string iterator } + ,iterator_intf + ,contnrs + ; + +type + + TTBStringsIterator = class(TInterfacedObject, ITBStringIterator, ITBStringAndObjectIterator) + private + FStrings: TStrings; + FCursor: Integer; + { Interface methods should always be private because + we will only ever access them via an Interface, + never via an Object instance } + + { Interface: ITBStringIterator and ITBStringAndObjectIterator } + function HasNext: Boolean; + function Next: string; + function HasPrevious: Boolean; + function Previous: string; + { Interface: ITBStringAndObjectIterator } + function HasNextObject: Boolean; + function NextObject: TObject; + function HasPreviousObject: Boolean; + function PreviousObject: TObject; + public + constructor CreateCustom(const ASource: TStrings); virtual; + end; + + + TTBListIterator = class(TInterfacedObject, ITBIterator) + private + FList: TList; + FCursor: Integer; + { Interface: ITBIterator } + function HasNext: Boolean; + function Next: TObject; + function HasPrevious: Boolean; + function Previous: TObject; + public + constructor CreateCustom(const ASource: TList); virtual; + end; + + + TTBCollectionIterator = class(TInterfacedObject, ITBIterator) + private + FCollection: TCollection; + FCursor: Integer; + { Interface: ITBIterator } + function HasNext: Boolean; + function Next: TObject; + function HasPrevious: Boolean; + function Previous: TObject; + public + constructor CreateCustom(const ASource: TCollection); virtual; + end; + + + TTBInterfaceListIterator = class(TInterfacedObject, ITBInterfaceIterator) + private + FList: TInterfaceList; + FCursor: integer; + { Interface: ITBinterfaceIterator } + function HasNext: Boolean; + function Next: IInterface; + function HasPrevious: Boolean; + function Previous: IInterface; + public + constructor CreateCustom(const ASource: TInterfaceList); virtual; + end; + + + TTBFilteredStringsIterator = class(TTBStringsIterator, ITBFilteredStringIterator) + private + FNextIndex: Integer; + FRegex: TRegexEngine; + { Interface: ITBFilteredStringIterator } + function GetFilter: string; + procedure SetFilter(const AValue: string); + { Interface: ITBStringIterator and ITBStringAndObjectIterator } + function HasNext: Boolean; + function Next: string; + function HasPrevious: Boolean; + function Previous: string; + public + constructor CreateCustom(const ASource: TStrings); override; + destructor Destroy; override; + end; + + + TTBObjectListIterator = class(TInterfacedObject, ITBIterator) + private + FList: TObjectList; + FCursor: Integer; + { Interface: ITBIterator } + function HasNext: Boolean; + function Next: TObject; + function HasPrevious: Boolean; + function Previous: TObject; + public + constructor CreateCustom(const ASource: TObjectList); virtual; + end; + + + +implementation + + +{ TTBStringsIterator } + +function TTBStringsIterator.HasNext: Boolean; +begin + Result := False; + if Assigned(FStrings) then + if FCursor < FStrings.Count - 1 then + Result := True; +end; + +function TTBStringsIterator.Next: string; +begin + Result := ''; + if HasNext then + begin + Inc(FCursor, 1); + Result := FStrings.Strings[FCursor]; + end; +end; + +function TTBStringsIterator.HasPrevious: Boolean; +begin + Result := False; + if Assigned(FStrings) then + if FCursor > 0 then + Result := True; +end; + +function TTBStringsIterator.Previous: string; +begin + Result := ''; + if HasPrevious then + begin + Dec(FCursor, 1); + Result := FStrings.Strings[FCursor]; + end; +end; + +function TTBStringsIterator.HasNextObject: Boolean; +begin + Result := False; + if Assigned(FStrings) then + if FCursor < FStrings.Count - 1 then + Result := FStrings.Objects[FCursor] <> nil; +end; + +function TTBStringsIterator.NextObject: TObject; +begin + Result := nil; + if HasNextObject then + // Note that Next(...) increments the FCursor + Result := FStrings.Objects[FCursor]; +end; + +function TTBStringsIterator.HasPreviousObject: Boolean; +begin + Result := False; + if Assigned(FStrings) then + if FCursor > 0 then + Result := FStrings.Objects[FCursor] <> nil; +end; + +function TTBStringsIterator.PreviousObject: TObject; +begin + Result := nil; + if HasPreviousObject then + // Note that Previous(...) decrements the FCursor + Result := FStrings.Objects[FCursor]; +end; + +constructor TTBStringsIterator.CreateCustom(const ASource: TStrings); +begin + inherited Create; + FStrings := ASource; + FCursor := -1; +end; + + +{ TTBListIterator } + +function TTBListIterator.HasNext: Boolean; +begin + Result := False; + if Assigned(FList) then + if FCursor < FList.Count - 1 then + Result := True; +end; + +function TTBListIterator.Next: TObject; +begin + Result := nil; + if HasNext then + begin + Inc(FCursor, 1); + result := TObject(FList.Items[FCursor]); + end; +end; + +function TTBListIterator.HasPrevious: Boolean; +begin + Result := False; + if Assigned(FList) then + begin + if FCursor > 0 then + Result := True; + end; +end; + +function TTBListIterator.Previous: TObject; +begin + Result := nil; + if HasPrevious then + begin + Dec(FCursor, 1); + Result := TObject(FList.Items[FCursor]); + end; +end; + +constructor TTBListIterator.CreateCustom(const ASource: TList); +begin + inherited Create; + FList := ASource; + FCursor := -1; +end; + + +{ TTBCollectionIterator } + +function TTBCollectionIterator.HasNext: Boolean; +begin + Result := False; + if Assigned(FCollection) then + if FCursor < FCollection.Count - 1 then + Result := True; +end; + +function TTBCollectionIterator.Next: TObject; +begin + Result := nil; + if HasNext then + begin + Inc(FCursor, 1); + result := FCollection.Items[FCursor]; + end; +end; + +function TTBCollectionIterator.HasPrevious: Boolean; +begin + Result := False; + if Assigned(FCollection) then + if FCursor > 0 then + Result := True; +end; + +function TTBCollectionIterator.Previous: TObject; +begin + Result := nil; + if HasPrevious then + begin + Dec(FCursor, 1); + Result := FCollection.Items[FCursor]; + end; +end; + +constructor TTBCollectionIterator.CreateCustom(const ASource: TCollection); +begin + inherited Create; + FCollection := ASource; + FCursor := -1; +end; + + +{ TTBFilteredStringsIterator } + +function TTBFilteredStringsIterator.GetFilter: string; +begin + Result := FRegex.RegexString; +end; + +procedure TTBFilteredStringsIterator.SetFilter(const AValue: string); +const + cFilterErr = 'Error in Filter string at position %d with ErrorCode %d. Filter string <%s>'; +var + LErrorCode: TRegexError; + LErrorPos: integer; +begin + if AValue <> FRegex.RegexString then + begin + FRegex.RegexString := AValue; + if not FRegex.Parse(LErrorPos, LErrorCode) then + raise Exception.CreateFmt(cFilterErr, [LErrorPos, Ord(LErrorCode), AValue]); + end; + FNextIndex := -1; +end; + +function TTBFilteredStringsIterator.HasNext: Boolean; +var + LIndex: integer; + LMatchPos: integer; + LOffset: integer; +begin + Result := False; + if GetFilter = '' then + begin + Result := inherited HasNext; + if Result then + FNextIndex := FCursor + 1; + end + else + begin + if FCursor < FStrings.Count - 1 then + begin + { If we haven't already calculated the next matching item } + if FNextIndex = -1 then + begin + LIndex := FCursor + 1; + { Peek ahead to find the next matching string } + while (LIndex < FStrings.Count) and (FNextIndex = -1) do + begin + { reset MatchString parameters } + LOffset := 0; + LMatchPos := 0; + if FRegex.MatchString(FStrings.Strings[LIndex], LMatchPos, LOffset) then + FNextIndex := LIndex; + Inc(LIndex); + end; + end; + if FNextIndex <> -1 then + Result := True; + end; + end; { if..else } +end; + +function TTBFilteredStringsIterator.Next: string; +begin + Result := ''; + if HasNext then + begin + FCursor := FNextIndex; + FNextIndex := -1; + Result := FStrings.Strings[FCursor]; + end; +end; + +function TTBFilteredStringsIterator.HasPrevious: Boolean; +begin + Result := False; // Filtered String is uni-directional +end; + +function TTBFilteredStringsIterator.Previous: string; +begin + Result := ''; + raise EUniDirectionalIterator.Create('Filtered String Iterator is uni-directional (forward) only.'); +end; + +constructor TTBFilteredStringsIterator.CreateCustom(const ASource: TStrings); +begin + inherited CreateCustom(ASource); + FRegex := TRegexEngine.Create(''); + FRegex.IgnoreCase := True; + FNextIndex := -1; +end; + +destructor TTBFilteredStringsIterator.Destroy; +begin + FRegex.Free; + inherited Destroy; +end; + + +{ TTBInterfaceListIterator } + +function TTBInterfaceListIterator.HasNext: Boolean; +begin + Result := False; + if Assigned(FList) then + if FCursor < FList.Count - 1 then + Result := True; +end; + +function TTBInterfaceListIterator.Next: IInterface; +begin + Result := nil; + if HasNext then + begin + Inc(FCursor, 1); + Result := FList.Items[FCursor]; + end; +end; + +function TTBInterfaceListIterator.HasPrevious: Boolean; +begin + Result := False; + if Assigned(FList) then + if FCursor > 0 then + Result := True; +end; + +function TTBInterfaceListIterator.Previous: IInterface; +begin + Result := nil; + if HasPrevious then + begin + Dec(FCursor, 1); + Result := FList.Items[FCursor]; + end; +end; + +constructor TTBInterfaceListIterator.CreateCustom(const ASource: TInterfaceList); +begin + inherited Create; + FList := ASource; + FCursor := -1; +end; + +{ TTBObjectListIterator } + +function TTBObjectListIterator.HasNext: Boolean; +begin + Result := False; + if Assigned(FList) then + if FCursor < FList.Count - 1 then + Result := True; +end; + +function TTBObjectListIterator.Next: TObject; +begin + result := nil; + if HasNext then + begin + Inc(FCursor); + result := FList.Items[FCursor]; + end; +end; + +function TTBObjectListIterator.HasPrevious: Boolean; +begin + Result := False; + if Assigned(FList) then + if FCursor > 0 then + Result := True; +end; + +function TTBObjectListIterator.Previous: TObject; +begin + result := nil; + if HasPrevious then + begin + Dec(FCursor); + result := FList.Items[FCursor]; + end; +end; + +constructor TTBObjectListIterator.CreateCustom(const ASource: TObjectList); +begin + inherited Create; + FList := ASource; + FCursor := -1; +end; + + +end. + diff --git a/docview/src/docdump/iterator_intf.pas b/docview/src/docdump/iterator_intf.pas new file mode 100644 index 00000000..e82b59a1 --- /dev/null +++ b/docview/src/docdump/iterator_intf.pas @@ -0,0 +1,169 @@ +unit iterator_intf; + +{$mode objfpc}{$H+} + +interface + +uses + Classes + ,SysUtils + ; + +type + { A custom exception class } + ENoIteratorImpl = class(Exception); + EUniDirectionalIterator = class(Exception); + + + { Standard iterators } + + ITBIterator = interface(IInterface) + ['{9C2BC10D-54C8-4B59-88B5-A564921CF0E3}'] + function HasNext: Boolean; + function Next: TObject; + function HasPrevious: Boolean; + function Previous: TObject; + end; + + + ITBStringIterator = interface(IInterface) + ['{B2A449B4-5D0A-4F14-AC11-CA055EDA3ED7}'] + function HasNext: Boolean; + function Next: string; + function HasPrevious: Boolean; + function Previous: string; + end; + + + ITBStringAndObjectIterator = interface(ITBStringIterator) + ['{287373DC-A90D-400E-BAEE-C85474C317A8}'] + function HasNextObject: Boolean; + function NextObject: TObject; + function HasPreviousObject: Boolean; + function PreviousObject: TObject; + end; + + + ITBInterfaceIterator = interface + ['{9B599C5B-4BBB-43F6-AF8E-09FEE9AE0E20}'] + function HasNext: Boolean; + function Next: IInterface; + function HasPrevious: Boolean; + function Previous: IInterface; + end; + + { TODO: + More interfaces could be added for collections like: + TTreeView, TStringGrid etc... } + + + { Filtered iterators } + + ITBFilteredStringIterator = interface(ITBStringIterator) + ['{CF1B9E2D-DD05-4D15-95C6-686EAFA4ED82}'] + function GetFilter: string; + procedure SetFilter(const AValue: string); + property Filter: string read GetFilter write SetFilter; + end; + + + { TODO: + More filtered versions of the standard iterators could + be added here... } + + + + { Iterator Factory } + + TTBIteratorFactory = class(TObject) + function Iterator(const ASource: TObject): ITBIterator; + function StringIterator(const ASource: TObject): ITBStringIterator; + function StringAndObjectIterator(const ASource: TObject): ITBStringAndObjectIterator; + function InterfaceIterator(const ASource: TObject): ITBInterfaceIterator; + function FilteredStringIterator(const ASource: TObject; const AFilter: string): ITBFilteredStringIterator; + end; + + +{ Global iterator factory singleton } +function gIteratorFactory: TTBIteratorFactory; + + +implementation + +uses + iterator_impl; + +var + uIteratorFactory: TTBIteratorFactory; + +const + cNoIteratorImpl = 'No Iterator implementation found for <%s>'; + + +{ The lazy mans singleton implementation, but it does the job just fine. } +function gIteratorFactory: TTBIteratorFactory; +begin + if not Assigned(uIteratorFactory) then + uIteratorFactory := TTBIteratorFactory.Create; + Result := uIteratorFactory; +end; + + +{ TTBIteratorFactory } + +function TTBIteratorFactory.Iterator(const ASource: TObject): ITBIterator; +begin + if ASource is TList then + Result := TTBListIterator.CreateCustom(TList(ASource)) + else if ASource is TCollection then + Result := TTBCollectionIterator.CreateCustom(TCollection(ASource)) + //else if ASource is TTreeNodes then + //Result := TTBTreeNodesIterator.CreateCustom(TTreeNodes(ASource)) + else + raise ENoIteratorImpl.CreateFmt(cNoIteratorImpl, [ASource.ClassName]); +end; + +function TTBIteratorFactory.StringIterator(const ASource: TObject): ITBStringIterator; +begin + if ASource is TStrings then + Result := TTBStringsIterator.CreateCustom(TStrings(ASource)) + else + raise ENoIteratorImpl.CreateFmt(cNoIteratorImpl, [ASource.ClassName]); +end; + +function TTBIteratorFactory.StringAndObjectIterator(const ASource: TObject): ITBStringAndObjectIterator; +begin + if ASource is TStrings then + Result := TTBStringsIterator.CreateCustom(TStrings(ASource)) + else + raise ENoIteratorImpl.CreateFmt(cNoIteratorImpl, [ASource.ClassName]); +end; + +function TTBIteratorFactory.InterfaceIterator(const ASource: TObject): ITBInterfaceIterator; +begin + if ASource is TInterfaceList then + Result := TTBInterfaceListIterator.CreateCustom(TInterfaceList(ASource)) + else + raise ENoIteratorImpl.CreateFmt(cNoIteratorImpl, [ASource.ClassName]); +end; + +function TTBIteratorFactory.FilteredStringIterator(const ASource: TObject; const AFilter: string): ITBFilteredStringIterator; +begin + if ASource is TStrings then + begin + Result := TTBFilteredStringsIterator.CreateCustom(TStrings(ASource)); + Result.Filter := AFilter; + end + else + raise ENoIteratorImpl.CreateFmt(cNoIteratorImpl, [ASource.ClassName]); +end; + + +initialization + uIteratorFactory := nil; + +finalization + uIteratorFactory.Free; + +end. + diff --git a/docview/src/docdump/readcontrols.pas b/docview/src/docdump/readcontrols.pas new file mode 100644 index 00000000..395a36db --- /dev/null +++ b/docview/src/docdump/readcontrols.pas @@ -0,0 +1,44 @@ +{ + Dump the controls data +} +unit readcontrols; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, filestreamhelper; + +procedure ProcessControls(AIn: TFileStream; AOut: TFileTextStream); + + +implementation + +uses + readheader, IPFFileFormatUnit; + +procedure ProcessControls(AIn: TFileStream; AOut: TFileTextStream); +var + ctrls: TPanelControls; + i: integer; +begin + AOut.WriteLn(''); + AOut.WriteLn('Panel Controls (Buttons)'); + if eHdr.CtrlOffset > 0 then + begin + AIn.Seek(eHdr.CtrlOffset, soBeginning); + AIn.Read(ctrls, SizeOf(TControlDef)); + AOut.WriteLn(Format(' PanelControls.ControlCount: %4.4x (%0:d)', [ctrls.ControlCount])); + AOut.WriteLn(Format(' PanelControls.GroupCount: %4.4x (%0:d)', [ctrls.GroupCount])); + AOut.WriteLn(Format(' PanelControls.GroupIndex: %4.4x (%0:d)', [ctrls.GroupIndex])); + AOut.WriteLn(Format(' PanelControls.Reserved: %4.4x (%0:d)', [ctrls.Reserved])); + AOut.WriteLn(' *****'); + AOut.WriteLn(' <todo - process CountrolCount and GroupCount data>'); + end + else + AOut.WriteLn(' No panel control found'); +end; + +end. + diff --git a/docview/src/docdump/readextfiles.pas b/docview/src/docdump/readextfiles.pas new file mode 100644 index 00000000..6ea979d9 --- /dev/null +++ b/docview/src/docdump/readextfiles.pas @@ -0,0 +1,53 @@ +{ + Dumps the names of external database (help) files referenced by this file +} +unit readextfiles; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, IPFFileFormatUnit, filestreamhelper; + +procedure ProcessExtFiles(AIn: TFileStream; AOut: TFileTextStream); + + +implementation + +uses + readheader; + +procedure ProcessExtFiles(AIn: TFileStream; AOut: TFileTextStream); +var + count: integer; + name: string; + pData: pointer; + p: pointer; + pLength: pByte; +begin + AOut.WriteLn(''); + AOut.WriteLn('External File References'); + + if eHdr.NumDataBase > 0 then + begin + pData := nil; + AIn.Seek(eHdr.DataBaseOffset, soBeginning); + GetMem(pData, eHdr.DataBaseSize); // allocate temp space for data + AIn.Read(pData^, eHdr.DataBaseSize); // read all data in one shot + p := pData; // p is our incrementing position in the data + for count := 0 to eHdr.NumDataBase-1 do + begin + pLength := p; // length byte, including itself + SetString(name, p+1, pLength^-1); // use length value minus the length byte to get the string length + AOut.WriteLn(Format(' File #%d: %s', [count, name])); + inc(p, pLength^); // skip to next entry using full length (including length byte) + end; + FreeMem(pData, eHdr.DataBaseSize); // free allocated space + end + else + AOut.WriteLn(' No external file references found'); +end; + +end. + diff --git a/docview/src/docdump/readfonts.pas b/docview/src/docdump/readfonts.pas new file mode 100644 index 00000000..6b684cac --- /dev/null +++ b/docview/src/docdump/readfonts.pas @@ -0,0 +1,47 @@ +{ + Dumps the font data +} +unit readfonts; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, filestreamhelper; + +procedure ProcessFonts(AIn: TFileStream; AOut: TFileTextStream); + + +implementation + +uses + readheader, IPFFileFormatUnit; + +procedure ProcessFonts(AIn: TFileStream; AOut: TFileTextStream); +var + fnt: THelpFontSpec; + pData: pointer; + i: integer; +begin + AOut.WriteLn(''); + AOut.WriteLn('Font Data'); + if eHdr.NumFontEntry > 0 then + begin + AIn.Seek(eHdr.FontTableOffset, soBeginning); + for i := 0 to eHdr.NumFontEntry-1 do + begin + AIn.Read(fnt, SizeOf(THelpFontSpec)); + AOut.WriteLn(Format(' Font Entry #%d', [i+1])); + AOut.WriteLn(Format(' FontSpec.FaceName: %s', [fnt.FaceName])); + AOut.WriteLn(Format(' FontSpec.Height: %4.4x (%0:d)', [fnt.Height])); + AOut.WriteLn(Format(' FontSpec.Width: %4.4x (%0:d)', [fnt.Width])); + AOut.WriteLn(Format(' FontSpec.CodePage: %4.4x (%0:d)', [fnt.Codepage])); + end; + end + else + AOut.WriteLn(' No font data is present'); +end; + +end. + diff --git a/docview/src/docdump/readheader.pas b/docview/src/docdump/readheader.pas new file mode 100644 index 00000000..201fcb86 --- /dev/null +++ b/docview/src/docdump/readheader.pas @@ -0,0 +1,135 @@ +{ + Dump the INF header & extended header structures to a text file +} +unit readheader; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, filestreamhelper, IPFFileFormatUnit; + +procedure ProcessHeader(AIn: TFileStream; AOut: TFileTextStream); + +var + hdr: THelpFileHeader; + eHdr: TExtendedHelpFileHeader; + + +implementation + + +type + TWord = record + b1: AnsiChar; + b2: AnsiChar; + end; + + TOverlayID = packed record + b1: Byte; + b2: Byte; + b3: Byte; + end; + + TOverlaySearchStart = bitpacked record + SearchOffset: Unsigned_31; + IsRec16bitSize: boolean; + end; + + +procedure ProcessHeader(AIn: TFileStream; AOut: TFileTextStream); +var + bytes: integer; + s: string; + w: TWord; + i: uint32; + t0: TOverlayID; + t1: TOverlaySearchStart; +begin + try + AIn.Seek(0, soBeginning); + bytes := AIn.Read(hdr, SizeOf(THelpFileHeader)); + if bytes <> SizeOf(THelpFileHeader) then + raise Exception.Create('Failed to read complete file header'); + + if hdr.ID <> INF_HEADER_ID then + raise Exception.Create('This is not an OS/2 help file'); + + AOut.WriteLn('Header Section'); + t0 := TOverlayID(hdr.ID); + s := hdr.ID; + AOut.WriteLn(Format(' ipfheader.id: %4.2x %2x %2x ("%s") : Magic word' ,[Byte(hdr.id[0]), Byte(hdr.id[1]), Byte(hdr.id[2]), s])); + if (hdr.flags and $01) > 0 then + s := 'INF' + else + s := 'HLP'; + AOut.WriteLn(Format(' ipfheader.flags: %8.2x (%s format) : File format' ,[hdr.flags, s])); + AOut.WriteLn(Format(' ipfheader.size: %8.4x (%0:7d bytes) : Size of this header structure', [hdr.hdrsize])); + AOut.WriteLn(Format(' ipfheader.version: %6d.%d : version of file format?', [hdr.version_hi, hdr.version_lo])); + AOut.WriteLn(Format(' ipfheader.ntoc: %8.4x (%0:13d) : No of TOC entries', [hdr.ntoc])); + AOut.WriteLn(Format(' ipfheader.tocstart: %8.8x (%0:7d bytes) : 32bit file offset to start of TOC', [hdr.tocstart])); + AOut.WriteLn(Format(' ipfheader.toclen: %8.8x (%0:7d bytes) : bytes occupied by TOC entries', [hdr.toclen])); + AOut.WriteLn(Format(' ipfheader.tocoffsetsstart: %8.8x (%0:7d bytes) : file offset to array of TOC offsets', [hdr.tocoffsetsstart])); + AOut.WriteLn(Format(' ipfheader.nres: %8.4x (%0:13d) : number of panels with resource numbers', [hdr.nres])); + AOut.WriteLn(Format(' ipfheader.resstart: %8.8x (%0:7d bytes) : 32bit file offset of ressource number table', [hdr.resstart])); + AOut.WriteLn(Format(' ipfheader.nname: %8.4x (%0:13d) : number of panels with textual name', [hdr.nname])); + AOut.WriteLn(Format(' ipfheader.namestart: %8.8x (%0:7d bytes) : 32bit file offset to panel name table', [hdr.namestart])); + AOut.WriteLn(Format(' ipfheader.nindex: %8.4x (%0:13d) : number of index entries', [hdr.nindex])); + AOut.WriteLn(Format(' ipfheader.indexstart: %8.8x (%0:7d bytes) : 32bit file offset to index table', [hdr.indexstart])); + AOut.WriteLn(Format(' ipfheader.indexlen: %8.8x (%0:7d bytes) : size of index table', [hdr.indexlen])); + AOut.WriteLn(Format(' ipfheader.icmdCount: %8.4x (%0:13d) : number of icmd index items', [hdr.icmdCount])); + AOut.WriteLn(Format(' ipfheader.icmdOffset: %8.8x (%0:7d bytes) : file offset to icmd index items', [hdr.icmdOffset])); + AOut.WriteLn(Format(' ipfheader.icmdSize: %8.8x (%0:7d bytes) : size of icmd index table', [hdr.icmdSize])); + t1 := TOverlaySearchStart(hdr.searchstart); + i := t1.SearchOffset; + AOut.WriteLn(Format(' ipfheader.searchstart :31 %8.8x (%0:7d bytes) : 31bit file offset of full text search table', [i, i])); + if t1.IsRec16bitSize then + s := 'search rec is 16bit size' + else + s := 'search rec is 8bit size'; + AOut.WriteLn(Format(' ipfheader.recSize :1 %s (%s) : if high bit set, search record size is 16bit', [BoolToStr(t1.IsRec16bitSize, True), s])); + AOut.WriteLn(Format(' ipfheader.searchlen: %8.8x (%0:7d bytes) : size of full text search table', [hdr.searchlen])); + AOut.WriteLn(Format(' ipfheader.nslots: %8.4x (%0:13d) : number of "slots"', [hdr.nslots])); + AOut.WriteLn(Format(' ipfheader.slotsstart: %8.8x (%0:7d bytes) : 32bit file offset of the slots array', [hdr.slotsstart])); + AOut.WriteLn(Format(' ipfheader.dictlen: %8.8x (%0:7d bytes) : bytes occupied by the "dictionary"', [hdr.dictlen])); + AOut.WriteLn(Format(' ipfheader.ndict: %8.4x (%0:13d) : number of entries in the dictionary', [hdr.ndict])); + AOut.WriteLn(Format(' ipfheader.dictstart: %8.8x (%0:7d bytes) : 32bit file offset to start of dictionary', [hdr.dictstart])); + AOut.WriteLn(Format(' ipfheader.imgstart: %8.8x (%0:7d bytes) : 32bit file offset to image data', [hdr.imgstart])); + AOut.WriteLn(Format(' ipfheader.maxCVTIndex: %8.2x (%0:13d) : highest index inside panel''s local dictionary', [hdr.maxCVTIndex])); + AOut.WriteLn(Format(' ipfheader.nlsstart: %8.8x (%0:7d bytes) : 32bit file offset of NLS table', [hdr.nlsstart, hdr.nlsstart])); + AOut.WriteLn(Format(' ipfheader.nlslen: %8.8x (%0:7d bytes) : size of NLS table', [hdr.nlslen])); + AOut.WriteLn(Format(' ipfheader.extstart: %8.8x (%0:7d bytes) : 32bit file offset of extended data block', [hdr.extstart])); + AOut.WriteLn(Format(' ipfheader.reserved: %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x %2.2x : for future use. set to zero.', + [hdr.reserved[0], hdr.reserved[1], hdr.reserved[2], hdr.reserved[3], hdr.reserved[4], hdr.reserved[5], + hdr.reserved[6], hdr.reserved[7], hdr.reserved[8], hdr.reserved[9], hdr.reserved[10], hdr.reserved[11] ])); + AOut.WriteLn(Format(' ipfheader.title: "%s" : ASCII title of database', [hdr.title])); + + AOut.WriteLn(''); + AOut.WriteLn('Extended Header Section'); + AIn.Seek(hdr.extstart, soBeginning); + AIn.Read(eHdr, SizeOf(TExtendedHelpFileHeader)); + AOut.WriteLn(Format(' extheader.NumFontEntry %8.4x (%0:13d) : Font Table - number of entries', [eHdr.NumFontEntry])); + AOut.WriteLn(Format(' extheader.FontTableOffset %8.8x (%0:7d bytes) : Font Table - 32bit offset in file', [eHdr.FontTableOffset])); + AOut.WriteLn(Format(' extheader.NumDataBase %8.4x (%0:13d) : Data Base - No of files', [eHdr.NumDataBase])); + AOut.WriteLn(Format(' extheader.DataBaseOffset %8.8x (%0:7d bytes) : Data Base - 32bit offset in file', [eHdr.DataBaseOffset])); + AOut.WriteLn(Format(' extheader.DataBaseSize %8.8x (%0:7d bytes) : Data Base - Size in bytse', [eHdr.DataBaseSize])); + AOut.WriteLn(Format(' extheader.EntryInGNameTable %8.4x (%0:13d) : Global Names - No entries', [eHdr.EntryInGNameTable])); + AOut.WriteLn(Format(' extheader.HelpPanelGNameTblOffset %8.8x (%0:7d bytes) : Global Names - 32bit offset in file', [eHdr.HelpPanelGNameTblOffset])); + AOut.WriteLn(Format(' extheader.StringsOffset %8.8x (%0:7d bytes) : Strings - 32bit offset in file', [eHdr.StringsOffset])); + AOut.WriteLn(Format(' extheader.StringsSize %8.4x (%0:7d bytes) : Strings - Total bytes of all strings', [eHdr.StringsSize])); + AOut.WriteLn(Format(' extheader.ChildPagesOffset %8.8x (%0:7d bytes) : Child Pages - 32bit offset in file', [eHdr.ChildPagesOffset])); + AOut.WriteLn(Format(' extheader.ChildPagesSize %8.8x (%0:7d bytes) : Child Pages - Total bytes of all strings', [eHdr.ChildPagesSize])); + AOut.WriteLn(Format(' extheader.NumGIndexEntry %8.8x (%0:13d) : Total number of Global Index items', [eHdr.NumGIndexEntry])); + AOut.WriteLn(Format(' extheader.CtrlOffset %8.8x (%0:7d bytes) : Ctrl Buttons : offset in file', [eHdr.CtrlOffset])); + AOut.WriteLn(Format(' extheader.CtrlSize %8.8x (%0:7d bytes) : Ctrl Buttons : size in bytes', [eHdr.CtrlSize])); + AOut.WriteLn(Format(' extheader.reserved: %8.8x %8.8x %8.8x %8.8x : for future use. set to zero.', + [eHdr.reserved[0], eHdr.reserved[1], eHdr.reserved[2], eHdr.reserved[3]])); + + finally + // no nothing + end; +end; + +end. + diff --git a/docview/src/docdump/readnlsdata.pas b/docview/src/docdump/readnlsdata.pas new file mode 100644 index 00000000..68609235 --- /dev/null +++ b/docview/src/docdump/readnlsdata.pas @@ -0,0 +1,31 @@ +unit readnlsdata; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, filestreamhelper; + +procedure ProcessNLSData(AIn: TFileStream; AOut: TFileTextStream); + +implementation + +uses + readheader; + +procedure ProcessNLSData(AIn: TFileStream; AOut: TFileTextStream); +begin + AOut.WriteLn(''); + AOut.WriteLn('NLS Data'); + if hdr.nlslen > 0 then + begin + AOut.WriteLn(' <todo - process NLS data>'); + + end + else + AOut.WriteLn('NLS Data is not present'); +end; + +end. + diff --git a/docview/src/docdump/readstrings.pas b/docview/src/docdump/readstrings.pas new file mode 100644 index 00000000..7b397408 --- /dev/null +++ b/docview/src/docdump/readstrings.pas @@ -0,0 +1,56 @@ +{ + Dump the String table data +} +unit readstrings; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, filestreamhelper; + +procedure ProcessStringsTable(AIn: TFileStream; AOut: TFileTextStream); + + +implementation + +uses + readheader; + +procedure ProcessStringsTable(AIn: TFileStream; AOut: TFileTextStream); +var + name: string; + pData: pointer; + p: pointer; + pLength: pByte; + bytes: integer; +begin + AOut.WriteLn(''); + AOut.WriteLn('Strings Data'); + + if eHdr.StringsSize > 0 then + begin + pData := nil; + AIn.Seek(eHdr.StringsOffset, soBeginning); + GetMem(pData, eHdr.StringsSize); // allocate temp space for data + AIn.Read(pData^, eHdr.StringsSize); // read all data in one shot + p := pData; // p is our incrementing position in the data + bytes := 0; + while bytes < eHdr.StringsSize do; + begin + pLength := p; // length byte, including itself + bytes := bytes + pLength^; + SetString(name, p+1, pLength^-1); // use length value minus the length byte to get the string length + AOut.WriteLn(Format(' %s', [name])); + inc(p, pLength^); // skip to next entry using full length (including length byte) + end; + FreeMem(pData, eHdr.StringsSize); // free allocated space + end + else + AOut.WriteLn(' There are no strings'); + +end; + +end. + diff --git a/docview/src/docdump/readtoc.pas b/docview/src/docdump/readtoc.pas new file mode 100644 index 00000000..0cfae395 --- /dev/null +++ b/docview/src/docdump/readtoc.pas @@ -0,0 +1,171 @@ +{ + Dump the Table of Contents data +} +unit readtoc; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, filestreamhelper; + +procedure ProcessTOC(AIn: TFileStream; AOut: TFileTextStream); + +implementation + +uses + IPFFileFormatUnit, readheader, u_Tools; + +type + TTOCOverlay = bitpacked record + length: uint8; // length of the entry including this byte (but not including extended data) + nestlevel: Unsigned_4; + unknown: boolean; + extended: boolean; + hidden: boolean; + haschildren: boolean; + numSlots: uint8; // number of "slots" occupied by the text for this toc entry + end; + + + TTOCExtendedOverlay = bitpacked record + setPos: boolean; + setSize: boolean; + setView: boolean; + setStyle: boolean; + noSearch: boolean; + noPrint: boolean; + setCtrl: boolean; + setTutor: boolean; + clear: boolean; + unknown1: Unsigned_1; + setGroup: boolean; + isParent: boolean; + unknown2: Unsigned_4; + end; + + +procedure ProcessTOC(AIn: TFileStream; AOut: TFileTextStream); +var + Count: integer; + VisCount: integer; + pOffsets: UInt32ArrayPointer; + toc: TTOCEntryStart; + olay: TTOCOverlay; + tocextolay: TTOCExtendedOverlay; + pData: pointer; + pEntry: pTTOCEntryStart; + pExtendedInfo: pExtendedTOCEntry; + p: PByte; + i: integer; + titleLen: integer; + title: string; +begin + AOut.WriteLn(''); + AOut.WriteLn('Table of Contents'); + VisCount := 0; + GetMem(pOffsets, SizeOf(uint32) * hdr.ntoc); + AIn.Seek(hdr.tocoffsetsstart, soBeginning); + AIn.Read(pOffsets^, SizeOf(uint32) * hdr.ntoc); // now we have array of toc offsets + + AIn.Seek(hdr.tocstart, soBeginning); + GetMem(pData, hdr.toclen); + AIn.Read(pData^, hdr.toclen); + pEntry := pData; + for count := 1 to hdr.ntoc do + begin +// AIn.Read(toc, SizeOf(TTOCEntryStart)); +// FillChar(olay, SizeOf(TTOCOverlay), 0); + p := PByte(pEntry) + sizeof(TTOCEntryStart); + i := Longint(p^); + + olay.extended := (pEntry^.flags and TOCEntryExtended ) = TOCEntryExtended; + olay.nestlevel := (pEntry^.flags and TOCEntryLevelMask); + olay.hidden := (pEntry^.flags and TOCEntryHidden) = TOCEntryHidden; + olay.haschildren := (pEntry^.flags and TOCEntryHasChildren) = TOCEntryHasChildren; + + AOut.WriteLn(Format(' TOC Entry #%d at offset %8.8x (%d bytes)', [count, p^, i])); + AOut.WriteLn(Format(' tocentry.length: %2.2x (%0:d bytes)', [pEntry^.length])); + AOut.WriteLn(Format(' tocentry.nestlevel: %d', [olay.nestlevel])); + AOut.WriteLn(Format(' tocentry.unknown: %s', [iif(olay.unknown, 'set', 'clear')])); + AOut.WriteLn(Format(' tocentry.extended: %s', [iif(olay.extended, 'yes', 'no')])); + AOut.WriteLn(Format(' tocentry.hidden: %s', [iif(olay.hidden, 'yes', 'no')])); + AOut.WriteLn(Format(' tocentry.haschildren: %s', [iif(olay.haschildren, 'yes', 'no')])); + AOut.WriteLn(Format(' tocentry.numSlots: %d', [pEntry^.numSlots])); + if not olay.hidden then + inc(VisCount); + if olay.extended then + begin + pExtendedInfo := pExtendedTOCEntry( p ); // next data to follow must be Extended TOC Entry + + AOut.WriteLn(' Extended TOC Entry'); + tocextolay := TTOCExtendedOverlay(pExtendedInfo^); + + AOut.Writeln(Format(' ExtTocEntry.setPos: %s', [iif(tocextolay.setPos, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.setSize: %s', [iif(tocextolay.setSize, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.setView: %s', [iif(tocextolay.setView, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.setStyle: %s', [iif(tocextolay.setStyle, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.noSearch: %s', [iif(tocextolay.noSearch, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.noPrint: %s', [iif(tocextolay.noPrint, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.setCtrl: %s', [iif(tocextolay.setCtrl, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.setTutor: %s', [iif(tocextolay.setTutor, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.clear: %s', [iif(tocextolay.clear, 'clear', 'set')])); + AOut.Writeln(Format(' ExtTocEntry.unknown1: %1.1x', [tocextolay.unknown1])); + AOut.Writeln(Format(' ExtTocEntry.setGroup: %s', [iif(tocextolay.setGroup, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.isParent: %s', [iif(tocextolay.isParent, 'yes', 'no')])); + AOut.Writeln(Format(' ExtTocEntry.unknown2: %1.1x', [tocextolay.unknown2])); + + inc( p, sizeof( TExtendedTOCEntry ) ); // move p past two flag bytes + + if ( pExtendedInfo^.w1 and 1 ) > 0 then + // skip position + inc( p, sizeof( THelpXYPair ) ); + + if ( pExtendedInfo^.w1 and 2 ) > 0 then + // skip size + inc( p, sizeof( THelpXYPair ) ); + + if ( pExtendedInfo^.w1 and 8 ) > 0 then + // skip window controls + inc( p, sizeof(word) ); // increment by 2 + + if ( pExtendedInfo^.w1 and $40 ) > 0 then + // skip something else, unknown... style? 2 bytes + inc( p, sizeof(word) ); // increment by 2 + + if ( pExtendedInfo^.w2 and 4 ) > 0 then + begin +// _ContentsGroupIndex := pUInt16(p)^; + // read group + inc( p, sizeof( uint16 ) ); + end; + end; + + // skip slot numbers for now. +// _pSlotNumbers := pUInt16(p); + inc( p, pEntry^.numSlots * sizeof(uint16) ); + + // Calculate the remainder of the tocentry length - that is the bytes used for TOC topic (title) text + titleLen := pEntry^.length - ( longword( p ) - longword( pEntry ) ); + + // Read title + if TitleLen > 0 then + SetString(Title, Pointer(p), TitleLen) + else + Title := '<unknown>'; + AOut.WriteLn(Format(' toc Title: %s', [title])); + + + p := PByte(pEntry); + inc(p, pEntry^.Length); + pEntry := pTTOCEntryStart(p); + end; + + AOut.WriteLn(Format(' TOC visible count: %d', [VisCount])); + FreeMem(pOffsets, SizeOf(uint32) * hdr.ntoc); + FreeMem(pData, hdr.toclen); +end; + +end. + diff --git a/docview/src/docdump/u_Tools.pas b/docview/src/docdump/u_Tools.pas new file mode 100644 index 00000000..96192db9 --- /dev/null +++ b/docview/src/docdump/u_Tools.pas @@ -0,0 +1,52 @@ +{ + This unit will grow to include all handy functions that can be used in + different Lazarus projects. + + There may be no links to other non-standard units! +} +unit u_Tools; + +{$mode objfpc}{$H+} + +interface + + { Missing iif() known from Visual Basic - return a string } + function iif(fCon: Boolean; sTrue, sFalse: String): String; + { Missing iif() known from Visual Basic - return an Integer } + function iif(fCon: Boolean; iTrue, iFalse: Integer): Integer; + { Missing iif() known from Visual Basic - return an Extended } + function iif(fCon: Boolean; iTrue, iFalse: Extended): Extended; + + +implementation +uses + SysUtils; + + +function iif(fCon: Boolean; sTrue, sFalse: String): String; +begin + if fCon then + Result := sTrue + else + Result := sFalse; +end; + +function iif(fCon: Boolean; iTrue, iFalse: Integer): Integer; +begin + if fCon then + Result := iTrue + else + Result := iFalse; +end; + +function iif(fCon: Boolean; iTrue, iFalse: Extended): Extended; +begin + if fCon then + Result := iTrue + else + Result := iFalse; +end; + + +end. + diff --git a/docview/src/docview.lpi b/docview/src/docview.lpi new file mode 100644 index 00000000..f64b028f --- /dev/null +++ b/docview/src/docview.lpi @@ -0,0 +1,210 @@ +<?xml version="1.0"?> +<CONFIG> + <ProjectOptions> + <Version Value="8"/> + <General> + <Flags> + <SaveOnlyProjectUnits Value="True"/> + </Flags> + <SessionStorage Value="InIDEConfig"/> + <MainUnit Value="0"/> + <TargetFileExt Value=""/> + <Title Value="docview"/> + <UseAppBundle Value="False"/> + </General> + <VersionInfo> + <StringTable Comments="" CompanyName="" FileDescription="" FileVersion="0.0.0.0" InternalName="" LegalCopyright="" LegalTrademarks="" OriginalFilename="" ProductName="" ProductVersion="0.0.0.0"/> + </VersionInfo> + <PublishOptions> + <Version Value="2"/> + <IgnoreBinaries Value="False"/> + <IncludeFileFilter Value="*.(pas|pp|inc|lfm|lpr|lrs|lpi|lpk|sh|xml)"/> + <ExcludeFileFilter Value="*.(bak|ppu|ppw|o|so);*~;backup"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + <LaunchingApplication PathPlusParams="/usr/X11R6/bin/xterm -T 'Lazarus Run Output' -e $(LazarusDir)/tools/runwait.sh $(TargetCmdLine)"/> + </local> + </RunParams> + <RequiredPackages Count="1"> + <Item1> + <PackageName Value="fpgui_toolkit"/> + </Item1> + </RequiredPackages> + <Units Count="30"> + <Unit0> + <Filename Value="docview.lpr"/> + <IsPartOfProject Value="True"/> + <UnitName Value="docview"/> + </Unit0> + <Unit1> + <Filename Value="frm_main.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_main"/> + </Unit1> + <Unit2> + <Filename Value="IPFEscapeCodes.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="IPFEscapeCodes"/> + </Unit2> + <Unit3> + <Filename Value="HelpTopic.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="HelpTopic"/> + </Unit3> + <Unit4> + <Filename Value="HelpFile.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="HelpFile"/> + </Unit4> + <Unit5> + <Filename Value="CompareWordUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="CompareWordUnit"/> + </Unit5> + <Unit6> + <Filename Value="SearchTable.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="SearchTable"/> + </Unit6> + <Unit7> + <Filename Value="TextSearchQuery.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="TextSearchQuery"/> + </Unit7> + <Unit8> + <Filename Value="nvUtilities.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="nvUtilities"/> + </Unit8> + <Unit9> + <Filename Value="../docs/inf04.txt"/> + <IsPartOfProject Value="True"/> + </Unit9> + <Unit10> + <Filename Value="SearchUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="SearchUnit"/> + </Unit10> + <Unit11> + <Filename Value="../components/richtext/RichTextStyleUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="RichTextStyleUnit"/> + </Unit11> + <Unit12> + <Filename Value="../components/richtext/CanvasFontManager.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="CanvasFontManager"/> + </Unit12> + <Unit13> + <Filename Value="../components/richtext/ACLStringUtility.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="ACLStringUtility"/> + </Unit13> + <Unit14> + <Filename Value="../components/richtext/RichTextDocumentUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="RichTextDocumentUnit"/> + </Unit14> + <Unit15> + <Filename Value="../components/richtext/RichTextView.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="RichTextView"/> + </Unit15> + <Unit16> + <Filename Value="../components/richtext/RichTextLayoutUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="RichTextLayoutUnit"/> + </Unit16> + <Unit17> + <Filename Value="../components/richtext/RichTextDisplayUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="RichTextDisplayUnit"/> + </Unit17> + <Unit18> + <Filename Value="IPFFileFormatUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="IPFFileFormatUnit"/> + </Unit18> + <Unit19> + <Filename Value="HelpWindowDimensions.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="HelpWindowDimensions"/> + </Unit19> + <Unit20> + <Filename Value="NewViewConstantsUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="NewViewConstantsUnit"/> + </Unit20> + <Unit21> + <Filename Value="SettingsUnit.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="SettingsUnit"/> + </Unit21> + <Unit22> + <Filename Value="dvconstants.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="dvConstants"/> + </Unit22> + <Unit23> + <Filename Value="dvHelpers.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="dvHelpers"/> + </Unit23> + <Unit24> + <Filename Value="HelpWindow.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="HelpWindow"/> + </Unit24> + <Unit25> + <Filename Value="../TODO.txt"/> + <IsPartOfProject Value="True"/> + </Unit25> + <Unit26> + <Filename Value="frm_configuration.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="frm_configuration"/> + </Unit26> + <Unit27> + <Filename Value="arrows.inc"/> + <IsPartOfProject Value="True"/> + </Unit27> + <Unit28> + <Filename Value="HelpBitmap.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="HelpBitmap"/> + </Unit28> + <Unit29> + <Filename Value="missing.inc"/> + <IsPartOfProject Value="True"/> + </Unit29> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="9"/> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)/"/> + <OtherUnitFiles Value="../components/richtext/"/> + <UnitOutputDirectory Value="units/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <CStyleOperator Value="False"/> + <IncludeAssertionCode Value="True"/> + <AllowLabel Value="False"/> + <CPPInline Value="False"/> + <UseAnsiStrings Value="False"/> + </SyntaxOptions> + </Parsing> + <CodeGeneration> + <Optimizations> + <OptimizationLevel Value="0"/> + </Optimizations> + </CodeGeneration> + <Other> + <CustomOptions Value="-O-"/> + <CompilerPath Value="$(CompPath)"/> + </Other> + </CompilerOptions> +</CONFIG> diff --git a/docview/src/docview.lpr b/docview/src/docview.lpr new file mode 100644 index 00000000..91e50323 --- /dev/null +++ b/docview/src/docview.lpr @@ -0,0 +1,41 @@ +program docview; + +{$mode objfpc}{$H+} + +uses + {$IFDEF UNIX}{$IFDEF UseCThreads} + cthreads, + {$ENDIF}{$ENDIF} + Classes, + fpg_main, frm_main, IPFEscapeCodes, HelpTopic, CompareWordUnit, SearchTable, + TextSearchQuery, nvUtilities, HelpFile, SearchUnit, + fpg_cmdlineparams, IPFFileFormatUnit, HelpWindowDimensions, + NewViewConstantsUnit, SettingsUnit, RichTextStyleUnit, CanvasFontManager, + ACLStringUtility, RichTextDocumentUnit, RichTextView, RichTextLayoutUnit, + RichTextDisplayUnit, dvconstants, dvHelpers, frm_configuration, HelpBitmap; + + +procedure MainProc; +var + frm: TMainForm; +begin + fpgApplication.Initialize; + + // always load custom style for help viewer + //if Assigned(fpgStyle) then + // fpgStyle.Free; + //fpgStyle := TMyStyle.Create; + + frm := TMainForm.Create(nil); + try + frm.Show; + fpgApplication.Run; + finally + frm.Free; + end; +end; + +begin + MainProc; +end. + diff --git a/docview/src/dvHelpers.pas b/docview/src/dvHelpers.pas new file mode 100644 index 00000000..c5486bb7 --- /dev/null +++ b/docview/src/dvHelpers.pas @@ -0,0 +1,50 @@ +unit dvHelpers; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +const + OWN_HELP_MARKER = '[DOCVIEWHELP]'; + + +function GetOwnHelpFileName: String; +function FindHelpFile(const AFilename: string): string; + + +implementation + +uses + fpg_utils + ; + + +function GetOwnHelpFileName: String; +//var +// tmpLanguage : String; +begin + //tmpLanguage := getLanguage; + //if tmpLanguage = '' then + //begin + // tmpLanguage := GetEnv(LanguageEnvironmentVar) + //end; + // + //result := FindDefaultLanguageHelpFile('NewView', tmpLanguage); + + { TODO -oGraeme -cown help : Improve own help file location } + result := fpgExtractFilePath(ParamStr(0)) + 'docview.inf'; +end; + +// Given a "filename" which may include a path, find it in various paths and extensions +function FindHelpFile(const AFilename: string): string; +begin + { TODO -ograemeg -csearch files : Implement searching know locations } + Result := AFileName; +end; + + +end. + diff --git a/docview/src/dvconstants.pas b/docview/src/dvconstants.pas new file mode 100644 index 00000000..626e0840 --- /dev/null +++ b/docview/src/dvconstants.pas @@ -0,0 +1,39 @@ +unit dvConstants; + +{$mode objfpc}{$H+} + +interface + +const + { DO NOT LOCALIZE } + HelpPathEnvironmentVar = 'HELP'; + BookshelfEnvironmentVar = 'BOOKSHELF'; + HELP_FILE_DELIMITER = '+'; + HELP_FILE_EXTENSION = ExtensionSeparator + 'hlp'; + INF_FILE_EXTENSION = ExtensionSeparator + 'inf'; + + +resourcestring + rsDVTitle = 'Documentation Viewer'; + rsDVSearchingMsg = 'Searching...'; + rsDVDisplaying = 'Displaying...'; + rsDVLoadingNotes = 'Loading notes...'; + rsDVDisplayContents = 'Display contents...'; + rsDVDisplayingFirstTopic = 'Display first topic...'; + rsDVDone = 'Done'; + rsDVNoFile = 'No file'; + rsDVOpenHelpFile = 'Open Help File'; + rsDVHelpFiles = 'Help Files'; + rsDVNoMatchesFound = '(No matches found for ''%s'')'; + rsDVSearchSyntaxError = 'Error in search syntax: '; + rsDVSearchFoundMsg = 'Found %d matches for '; + rsDVCouldNotOpen = 'Could not open <%s>'; + +const + hcConfigGeneralTab = 510; + hcConfigFontsColorTab = 520; + +implementation + +end. + diff --git a/docview/src/extrafpc.cfg b/docview/src/extrafpc.cfg new file mode 100644 index 00000000..b68bb1b4 --- /dev/null +++ b/docview/src/extrafpc.cfg @@ -0,0 +1,10 @@ +-Sa +-gl +-Xs +-l +-Fiunits/ +-Fu../components/richtext/ +-Fu../../lib/$fpctarget +-Fu. +-FUunits + diff --git a/docview/src/frm_configuration.pas b/docview/src/frm_configuration.pas new file mode 100644 index 00000000..2e19939b --- /dev/null +++ b/docview/src/frm_configuration.pas @@ -0,0 +1,412 @@ +unit frm_configuration; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_tab, fpg_button, + fpg_label, fpg_edit, fpg_panel, fpg_combobox, fpg_listbox, fpg_checkbox; + +type + + TConfigurationForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: ConfigurationForm} + PageControl1: TfpgPageControl; + btnSave: TfpgButton; + btnCancel: TfpgButton; + tsGeneral: TfpgTabSheet; + tsFontsColor: TfpgTabSheet; + Label1: TfpgLabel; + edtNormalFont: TfpgEdit; + btnNormalFont: TfpgButton; + Label2: TfpgLabel; + edtFixedFont: TfpgEdit; + btnFixedFont: TfpgButton; + btnHelp: TfpgButton; + Panel1: TfpgPanel; + cbIndexStyle: TfpgComboBox; + lblIndexStyle: TfpgLabel; + lblSearchDirs: TfpgLabel; + btnSearchDirAdd: TfpgButton; + lbSearchDirs: TfpgListBox; + btnSearchDirDelete: TfpgButton; + chkEscapeIPFSymbols: TfpgCheckBox; + {@VFD_HEAD_END: ConfigurationForm} + procedure btnNormalFontClicked(Sender: TObject); + procedure btnFixedFontClicked(Sender: TObject); + procedure ConfigurationFormShow(Sender: TObject); + procedure btnHelpClick(Sender: TObject); + procedure PageControl1Change(Sender: TObject; NewActiveSheet: TfpgTabSheet); + procedure btnCancelClick(Sender: TObject); + procedure btnSaveClick(Sender: TObject); + procedure btnSearchDirAddClicked(Sender: TObject); + procedure SettingsToGui; + procedure GuiToSettings; + public + constructor Create(AOwner: TComponent); override; + procedure AfterCreate; override; + end; + +{@VFD_NEWFORM_DECL} + +procedure ShowConfigForm; + +implementation + +uses + fpg_dialogs, SettingsUnit, dvConstants; + +procedure ShowConfigForm; +var + frm: TConfigurationForm; +begin + frm := TConfigurationForm.Create(nil); + try + frm.ShowModal; + finally + frm.Free; + end; +end; + +{@VFD_NEWFORM_IMPL} + +procedure TConfigurationForm.btnNormalFontClicked(Sender: TObject); +var + f: TfpgString; +begin + f := edtNormalFont.Text; + if SelectFontDialog(f) then + edtNormalFont.Text := f; +end; + +procedure TConfigurationForm.btnFixedFontClicked(Sender: TObject); +var + f: TfpgString; +begin + f := edtFixedFont.Text; + if SelectFontDialog(f) then + edtFixedFont.Text := f; +end; + +procedure TConfigurationForm.ConfigurationFormShow(Sender: TObject); +begin + SettingsToGui; + PageControl1.ActivePage := tsGeneral; + // programatically seting a tab does not fire OnChange event, so we do it mantually + PageControl1Change(self, tsGeneral); +end; + +procedure TConfigurationForm.btnHelpClick(Sender: TObject); +begin + ShowMessage(IntToStr(btnHelp.HelpContext)); +end; + +procedure TConfigurationForm.PageControl1Change(Sender: TObject; + NewActiveSheet: TfpgTabSheet); +begin + if NewActiveSheet = tsGeneral then + begin + btnHelp.HelpContext := hcConfigGeneralTab; + end + else if NewActiveSheet = tsFontsColor then + begin + btnHelp.HelpContext := hcConfigFontsColorTab; + end; +end; + +procedure TConfigurationForm.btnCancelClick(Sender: TObject); +begin + ModalResult := mrCancel; +end; + +procedure TConfigurationForm.btnSaveClick(Sender: TObject); +begin + GuiToSettings; + SaveSettings; + ModalResult := mrOK; +end; + +procedure TConfigurationForm.btnSearchDirAddClicked(Sender: TObject); +var + s: TfpgString; +begin + s := SelectDirDialog(''); + if s <> '' then + lbSearchDirs.Items.Add(s); +end; + +procedure TConfigurationForm.SettingsToGui; +begin + // General + cbIndexStyle.FocusItem := Ord(Settings.IndexStyle); + lbSearchDirs.Items.Assign(Settings.SearchDirectories); + chkEscapeIPFSymbols.Checked := Settings.IPFTopicSaveAsEscaped; + // Fonts & Color + edtNormalFont.Text := Settings.NormalFont.FontDesc; + edtFixedFont.Text := Settings.FixedFont.FontDesc; +end; + +procedure TConfigurationForm.GuiToSettings; +begin + // General + Settings.IndexStyle := TIndexStyle(cbIndexStyle.FocusItem); + Settings.SearchDirectories.Assign(lbSearchDirs.Items); + Settings.IPFTopicSaveAsEscaped := chkEscapeIPFSymbols.Checked; + // Fonts & Color + Settings.NormalFont.Free; + Settings.NormalFont := fpgGetFont(edtNormalFont.Text); + Settings.FixedFont.Free; + Settings.FixedFont := fpgGetFont(edtFixedFont.Text); +end; + +constructor TConfigurationForm.Create(AOwner: TComponent); +begin + inherited Create(AOwner); + OnShow := @ConfigurationFormShow; +end; + +procedure TConfigurationForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: ConfigurationForm} + Name := 'ConfigurationForm'; + SetPosition(310, 157, 515, 439); + WindowTitle := 'Configuration'; + WindowPosition := wpOneThirdDown; + + PageControl1 := TfpgPageControl.Create(self); + with PageControl1 do + begin + Name := 'PageControl1'; + SetPosition(4, 4, 506, 388); + Anchors := [anLeft,anRight,anTop,anBottom]; + ActivePageIndex := 0; + TabOrder := 0; + OnChange := @PageControl1Change; + end; + + btnSave := TfpgButton.Create(self); + with btnSave do + begin + Name := 'btnSave'; + SetPosition(344, 408, 80, 24); + Text := 'Save'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 1; + OnClick := @btnSaveClick; + end; + + btnCancel := TfpgButton.Create(self); + with btnCancel do + begin + Name := 'btnCancel'; + SetPosition(428, 408, 80, 24); + Text := 'Cancel'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 2; + OnClick := @btnCancelClick; + end; + + tsGeneral := TfpgTabSheet.Create(PageControl1); + with tsGeneral do + begin + Name := 'tsGeneral'; + SetPosition(3, 24, 500, 361); + Text := 'General'; + end; + + tsFontsColor := TfpgTabSheet.Create(PageControl1); + with tsFontsColor do + begin + Name := 'tsFontsColor'; + SetPosition(3, 24, 500, 361); + Text := 'Fonts & Color'; + end; + + Label1 := TfpgLabel.Create(tsFontsColor); + with Label1 do + begin + Name := 'Label1'; + SetPosition(12, 20, 108, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Normal Font'; + end; + + edtNormalFont := TfpgEdit.Create(tsFontsColor); + with edtNormalFont do + begin + Name := 'edtNormalFont'; + SetPosition(124, 16, 248, 24); + Anchors := [anLeft,anRight,anTop]; + TabOrder := 1; + Text := ''; + FontDesc := '#Edit1'; + end; + + btnNormalFont := TfpgButton.Create(tsFontsColor); + with btnNormalFont do + begin + Name := 'btnNormalFont'; + SetPosition(384, 16, 80, 24); + Anchors := [anRight,anTop]; + Text := 'Select...'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 2; + OnClick := @btnNormalFontClicked; + end; + + Label2 := TfpgLabel.Create(tsFontsColor); + with Label2 do + begin + Name := 'Label2'; + SetPosition(12, 52, 104, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Fixed Font'; + end; + + edtFixedFont := TfpgEdit.Create(tsFontsColor); + with edtFixedFont do + begin + Name := 'edtFixedFont'; + SetPosition(124, 48, 248, 24); + Anchors := [anLeft,anRight,anTop]; + TabOrder := 4; + Text := ''; + FontDesc := '#Edit1'; + end; + + btnFixedFont := TfpgButton.Create(tsFontsColor); + with btnFixedFont do + begin + Name := 'btnFixedFont'; + SetPosition(384, 48, 80, 24); + Anchors := [anRight,anTop]; + Text := 'Select...'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 5; + OnClick :=@btnFixedFontClicked; + end; + + btnHelp := TfpgButton.Create(self); + with btnHelp do + begin + Name := 'btnHelp'; + SetPosition(468, 356, 28, 24); + Anchors := [anRight,anBottom]; + Text := '?'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 6; + HelpType := htContext; + OnClick := @btnHelpClick; + end; + + Panel1 := TfpgPanel.Create(tsFontsColor); + with Panel1 do + begin + Name := 'Panel1'; + SetPosition(128, 116, 204, 28); + FontDesc := '#Label1'; + Style := bsLowered; + Text := 'Panel'; + end; + + cbIndexStyle := TfpgComboBox.Create(tsGeneral); + with cbIndexStyle do + begin + Name := 'cbIndexStyle'; + SetPosition(12, 32, 160, 22); + FontDesc := '#List'; + Items.Add('Alphabetical'); + Items.Add('FileOnly'); + Items.Add('Full'); + TabOrder := 0; + end; + + lblIndexStyle := TfpgLabel.Create(tsGeneral); + with lblIndexStyle do + begin + Name := 'lblIndexStyle'; + SetPosition(12, 12, 212, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Index style'; + end; + + lblSearchDirs := TfpgLabel.Create(tsGeneral); + with lblSearchDirs do + begin + Name := 'lblSearchDirs'; + SetPosition(12, 64, 216, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Search directories'; + end; + + btnSearchDirAdd := TfpgButton.Create(tsGeneral); + with btnSearchDirAdd do + begin + Name := 'btnSearchDirAdd'; + SetPosition(408, 84, 80, 24); + Text := 'Add...'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 4; + OnClick :=@btnSearchDirAddClicked; + end; + + lbSearchDirs := TfpgListBox.Create(tsGeneral); + with lbSearchDirs do + begin + Name := 'lbSearchDirs'; + SetPosition(12, 84, 388, 148); + FontDesc := '#List'; + HotTrack := False; + PopupFrame := False; + TabOrder := 5; + Items.Duplicates := dupIgnore; + end; + + btnSearchDirDelete := TfpgButton.Create(tsGeneral); + with btnSearchDirDelete do + begin + Name := 'btnSearchDirDelete'; + SetPosition(408, 116, 80, 24); + Text := 'Remove...'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 5; + end; + + chkEscapeIPFSymbols := TfpgCheckBox.Create(tsGeneral); + with chkEscapeIPFSymbols do + begin + Name := 'chkEscapeIPFSymbols'; + SetPosition(12, 244, 480, 20); + Anchors := [anLeft,anRight,anTop]; + FontDesc := '#Label1'; + TabOrder := 6; + Text := 'Escape symbols when saving topics as IPF text'; + end; + + {@VFD_BODY_END: ConfigurationForm} + {%endregion} +end; + + +end. diff --git a/docview/src/frm_main.pas b/docview/src/frm_main.pas new file mode 100644 index 00000000..3e24c320 --- /dev/null +++ b/docview/src/frm_main.pas @@ -0,0 +1,2753 @@ +unit frm_main; + +{$mode objfpc}{$H+} + +interface + +uses + SysUtils, Classes, fpg_base, fpg_main, fpg_form, fpg_panel, fpg_tab, + fpg_tree, fpg_splitter, fpg_menu, fpg_button, fpg_listbox, + fpg_label, fpg_edit, fpg_radiobutton, fpg_progressbar, fpg_imagelist, + fpg_imgfmt_bmp, + HelpFile, RichTextView, HelpTopic; + +type + // Used by Index ListBox. We can generate a custom Index if the help file + // doesn't contain it's own index entries. + TListType = ( ltContents, ltIndex ); + + TMainForm = class(TfpgForm) + private + {@VFD_HEAD_BEGIN: MainForm} + bvlStatusBar: TfpgBevel; + ProgressBar: TfpgProgressBar; + lblStatus: TfpgLabel; + bvlBody: TfpgBevel; + PageControl1: TfpgPageControl; + tsContents: TfpgTabSheet; + tvContents: TfpgTreeView; + btnGo: TfpgButton; + tsIndex: TfpgTabSheet; + btnIndex: TfpgButton; + lbIndex: TfpgListBox; + IndexSearchEdit: TfpgEdit; + tsSearch: TfpgTabSheet; + Label1: TfpgLabel; + edSearchText: TfpgEdit; + Label2: TfpgLabel; + RadioButton1: TfpgRadioButton; + RadioButton2: TfpgRadioButton; + RadioButton3: TfpgRadioButton; + RadioButton4: TfpgRadioButton; + RadioButton5: TfpgRadioButton; + RadioButton6: TfpgRadioButton; + lbSearchResults: TfpgListBox; + Label3: TfpgLabel; + btnSearch: TfpgButton; + tsNotes: TfpgTabSheet; + ListBox1: TfpgListBox; + btnNotesAdd: TfpgButton; + btnNotesEdit: TfpgButton; + btnNotesDel: TfpgButton; + btnNotesGoto: TfpgButton; + tsHistory: TfpgTabSheet; + lbHistory: TfpgListBox; + Splitter1: TfpgSplitter; + RichView: TRichTextView; + MainMenu: TfpgMenuBar; + miFile: TfpgPopupMenu; + miSettings: TfpgPopupMenu; + miBookmarks: TfpgPopupMenu; + miHelp: TfpgPopupMenu; + miDebug: TfpgPopupMenu; + ToolBar: TfpgBevel; + btnOpen: TfpgButton; + btnBack: TfpgButton; + btnFwd: TfpgButton; + btnPrev: TfpgButton; + btnNext: TfpgButton; + btnHelp: TfpgButton; + btnQuit: TfpgButton; + Bevel1: TfpgBevel; + Bevel2: TfpgBevel; + {@VFD_HEAD_END: MainForm} + miOpenRecentMenu: TfpgPopupMenu; + miDebugHexInfo: TfpgMenuItem; +// Files: TList; // current open help files. + Debug: boolean; + FFileOpenRecent: TfpgMenuItem; + FHistorySelection: Boolean; + FImages: TfpgImageList; + + LoadingFilenameList: TStringList; + LoadingFileIndex: integer; + LoadingTotalSize: longint; + LoadingSizeSoFar: longint; + AllFilesWordSequences: TList; // of lists; one per open file; of possible word sequences + CurrentOpenFiles: TList; // current open help files. + MainTitle: string; + InIndexSearch: boolean; // true while searching index + IndexLoaded: boolean; + ContentsLoaded: boolean; + DisplayedIndex: TStringList; // duplicate of index listbox, for fast case insensitive searching + CurrentTopic: TTopic; // so we can get easy access to current topic viewed + CurrentHistoryIndex: integer; + + procedure btnBackHistClick(Sender: TObject); + procedure btnFwdHistClick(Sender: TObject); + procedure btnPrevClick(Sender: TObject); + procedure btnNextClick(Sender: TObject); + procedure RichViewClickLink(Sender: TRichTextView; Link: string); + procedure IndexSearchEditKeyPress(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure MainFormShow(Sender: TObject); + procedure MainFormDestroy(Sender: TObject); + procedure miFileQuitClicked(Sender: TObject); + procedure miFileOpenClicked(Sender: TObject); + procedure miFileOpenSpecialClicked(Sender: TObject); + procedure miFileCloseClicked(Sender: TObject); + procedure miConfigureClicked(Sender: TObject); + procedure miHelpProdInfoClicked(Sender: TObject); + procedure miHelpAboutFPGui(Sender: TObject); + procedure miHelpCmdLineParams(Sender: TObject); + procedure miDebugHeader(Sender: TObject); + procedure miDebugHex(Sender: TObject); + procedure miFileSaveTopicAsIPF(Sender: TObject); + procedure OnMRUMenuItemClick(Sender: TObject); + procedure btnShowIndex(Sender: TObject); + procedure btnGoClicked(Sender: TObject); + procedure tvContentsChange(Sender: TObject); + procedure edSearchTextKeyPress(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure lbSearchResultsKeyPress(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure MainFormException(Sender: TObject; E: Exception); + procedure MainFormCloseQuery(Sender: TObject; var CanClose: boolean); + procedure PageControl1Change(Sender: TObject; NewActiveSheet: TfpgTabSheet); + procedure tvContentsDoubleClick(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); + procedure lbIndexDoubleClick(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); + procedure lbIndexKeyPress(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure lbSearchResultsDoubleClick(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); + procedure lbHistoryDoubleClick(Sender: TObject; AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); + procedure lbHistoryKeyPress(Sender: TObject; var KeyCode: word; var ShiftState: TShiftState; var Consumed: boolean); + procedure btnSearchClicked(Sender: TObject); + procedure IndexSearchEditOnChange(Sender: TObject); + procedure DisplaySelectedSearchResultTopic; + procedure NavigateToHistoryIndex(AIndex: integer); + procedure UpdateLocationPanel; + procedure EnableControls; + procedure ClearAllWordSequences; + procedure DoSearch; + procedure SetWaitCursor; + procedure ClearWaitCursor; + procedure SetMainCaption; + procedure DisplayFiles(NewFiles: TList; var FirstContentsNode: TfpgTreeNode); + procedure FileOpen; + function LoadFiles(const aFileNames: TStrings; aHelpFiles: TList): boolean; + function OpenFiles(const FileNames: TStrings; const AWindowTitle: string; const DisplayFirstTopic: boolean): boolean; + function OpenFile(const AFileName: string; const AWindowTitle: string; const DisplayFirstTopic: boolean): boolean; + procedure CloseFile(const ADestroying: boolean = False); + procedure OnHelpFileLoadProgress(n, outof: integer; AMessage: string); + procedure LoadNotes(AHelpFile: THelpFile); + procedure LoadContents(AFiles: TList; var FirstNode: TfpgTreeNode); + procedure LoadIndex; + // Used in loading contents + procedure AddChildNodes(AHelpFile: THelpFile; AParentNode: TfpgTreeNode; ALevel: longint; var ATopicIndex: longint ); + procedure ClearNotes; + procedure ClearIndexComponents; + procedure SaveNotes(AHelpFile: THelpFile); + procedure DisplayTopic(ATopic: TTopic = nil); + procedure ResetProgress; + procedure SetStatus(const AText: TfpgString); + function TranslateEnvironmentVar(AFilenames: TfpgString): TfpgString; + procedure RefreshFontSubstitutions; + procedure DisplaySelectedContentsTopic; + procedure DisplaySelectedIndexTopic; + procedure ProcessCommandLineParams; + procedure SaveNavigatePoint; + procedure ShowCmdLineParamHelp; + function FindTopicForLink( Link: THelpLink ): TTopic; + function FindTopicByResourceID( ID: word ): TTopic; + function FindTopicByName(const AName: string): TTopic; + procedure AddCurrentToMRUFiles; + procedure CreateMRUMenuItems; + public + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure AfterCreate; override; + end; + +{@VFD_NEWFORM_DECL} + + +implementation + +uses + fpg_dialogs + ,fpg_constants + ,fpg_iniutils + ,fpg_cmdlineparams + ,fpg_utils + ,fpg_stringutils + ,nvUtilities + ,ACLStringUtility + ,TextSearchQuery + ,SearchUnit + ,dvconstants + ,IPFFileFormatUnit + ,SettingsUnit + ,dvHelpers + ,frm_configuration + ; + +const + cLongName = 'fpGUI Documentation Viewer'; + cCreatedBy = 'Created by Graeme Geldenhuys'; + cVersion = 'Version ' + FPGUI_VERSION; + +{$I arrows.inc} +{$I missing.inc} + +{@VFD_NEWFORM_IMPL} + +procedure TMainForm.MainFormException(Sender: TObject; E: Exception); +begin + TfpgMessageDialog.Critical('An unexpected error occurred.', E.Message); +end; + +procedure TMainForm.lbIndexKeyPress(Sender: TObject; var KeyCode: word; + var ShiftState: TShiftState; var Consumed: boolean); +begin + if (KeyCode = keyReturn) or (KeyCode = keyPEnter) then + begin + Consumed := True; + DisplayTopic(nil); + end +end; + +procedure TMainForm.btnBackHistClick(Sender: TObject); +begin + if CurrentHistoryIndex > 0 then + begin + NavigateToHistoryIndex(CurrentHistoryIndex - 1); + //lbHistory.FocusItem := CurrentHistoryIndex - 1; + //DisplayTopic(TTopic(lbHistory.Items.Objects[lbHistory.FocusItem])); + end; +end; + +procedure TMainForm.btnFwdHistClick(Sender: TObject); +begin + if CurrentHistoryIndex < lbHistory.Items.Count-1 then + begin + NavigateToHistoryIndex(CurrentHistoryIndex + 1); + //lbHistory.FocusItem := CurrentHistoryIndex + 1; + //DisplayTopic(TTopic(lbHistory.Items.Objects[lbHistory.FocusItem])); + end; +end; + +procedure TMainForm.btnPrevClick(Sender: TObject); +begin + if CurrentOpenFiles.Count > 0 then + begin + tvContents.GotoNextNodeUp; + tvContents.SetFocus; + DisplaySelectedContentsTopic; + end; +end; + +procedure TMainForm.btnNextClick(Sender: TObject); +begin + if CurrentOpenFiles.Count > 0 then + begin + tvContents.GotoNextNodeDown; + tvContents.SetFocus; + DisplaySelectedContentsTopic; + end; +end; + +procedure TMainForm.RichViewClickLink(Sender: TRichTextView; Link: string); +var + LinkIndex: integer; + lLink: THelpLink; + lHelp: THelpFile; + i: integer; + lTopic: TTopic; + lFound: Boolean; + lURL: TfpgString; +begin + // TODO: process other types of links (external, application etc...) too! + if pos('external', Link) > 0 then + begin + TfpgMessageDialog.Warning('', 'External links are not supported in DocView yet. Please try again with a later build.') + end + else if pos('url', Link) > 0 then + begin + // we have a external URL of some kind + // format is always: 'url "<uri>"' +// ShowMessage('Found an external Link' + LineEnding + Link); + lURL := StringReplace(Link, 'url "', '', []); + lURL := UTF8Copy(lURL, 0, UTF8Length(lURL)-1); + fpgOpenURL(lURL); + end + else + begin + // we have a internal INF file link + LinkIndex := StrToInt( Link ); + lLink := THelpLink(CurrentTopic.Links[LinkIndex]); + lTopic := FindTopicForLink(lLink); + if lTopic <> nil then + DisplayTopic(lTopic); + exit; + + lHelp := THelpFile(lLink.HelpFile); + lTopic := nil; + lFound := False; + for i := 0 to CurrentOpenFiles.Count-1 do + begin + lHelp := THelpFile(CurrentOpenFiles[i]); + lTopic := lHelp.Topics[LinkIndex]; + if lTopic <> nil then + begin + lFound := True; +// writeln('Found Topic! ', lTopic.Title); + break; + end; + if lFound then + break; + end; + if lTopic <> nil then + begin +// writeln('Displaying topic <', lTopic.Title, '>'); + DisplayTopic(lTopic); + end; + end; +end; + +procedure TMainForm.IndexSearchEditKeyPress(Sender: TObject; var KeyCode: word; + var ShiftState: TShiftState; var Consumed: boolean); +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + if (KeyCode = keyEnter) or (KeyCode = keyPEnter) then + begin + Consumed := True; + DisplaySelectedIndexTopic; + end; +end; + +procedure TMainForm.MainFormShow(Sender: TObject); +begin + bvlBody.Realign; + + // restore previous window position and size + gINI.ReadFormState(self); + PageControl1.Width := gINI.ReadInteger('Options', 'SplitterLeft', 260); + UpdateWindowPosition; + + Settings.NormalFont := fpgStyle.DefaultFont; + Settings.FixedFont := fpgStyle.FixedFont; + Settings.SearchDirectories := TStringList.Create; + + LogEvent(LogSettings, 'Loading settings'); + LoadSettings; + CreateMRUMenuItems; + ProcessCommandLineParams; + + RichView.Images := FImages; +end; + +procedure TMainForm.MainFormDestroy(Sender: TObject); +begin + ClearAllWordSequences; + DisplayedIndex.Free; + // save splitter position + gINI.WriteInteger('Options', 'SplitterLeft', PageControl1.Width); + // save form size and position + gINI.WriteFormState(self); + LogEvent(LogSettings, 'Save settings'); + SaveSettings; + LogEvent(LogSettings, 'Save settings done'); +end; + +procedure TMainForm.miFileQuitClicked(Sender: TObject); +begin + Close; +end; + +procedure TMainForm.miFileOpenClicked(Sender: TObject); +begin + FileOpen; +end; + +procedure TMainForm.miFileOpenSpecialClicked(Sender: TObject); +var + s: TfpgString; +begin + if fpgInputQuery('Open Special...', 'Enter environment variable to open', s) then + begin + if s <> '' then + OpenFile(s, '', True); + end; +end; + +procedure TMainForm.miFileCloseClicked(Sender: TObject); +begin + CloseFile; +end; + +procedure TMainForm.miConfigureClicked(Sender: TObject); +begin + ShowConfigForm; +end; + +procedure TMainForm.miHelpProdInfoClicked(Sender: TObject); +var + s: TfpgString; +begin + s := cLongName + LineEnding + LineEnding + + cCreatedBy + LineEnding + + cVersion + ' - '+ {$I %date%} + ' ' + {$I %time%}; + + TfpgMessageDialog.Information('Product Information', s); +end; + +procedure TMainForm.miHelpAboutFPGui(Sender: TObject); +begin + TfpgMessageDialog.AboutFPGui; +end; + +procedure TMainForm.miHelpCmdLineParams(Sender: TObject); +begin + CloseFile(False); + ShowCmdLineParamHelp; +end; + +procedure TMainForm.miDebugHeader(Sender: TObject); +var + f: THelpFile; + i: integer; + sl: TStringList; +begin + RichView.Clear; + sl := TStringList.Create; + RichView.AddText(PChar('FileCount = ' + IntToStr(CurrentOpenFiles.Count))); + for i := 0 to CurrentOpenFiles.Count-1 do + begin + f := THelpFile(CurrentOpenFiles[i]); + sl.Clear; + with sl do + begin + Add('<b><u>Filename:</u></b> <red>' + f.Filename + '<black>'); + Add(''); + Add('<b>Title:</b> ' + f.Title); + Add('<b>Dictionary count:</b> ' + IntToStr(f.DictionaryCount)); + Add('<b>Topic count:</b> ' + IntToStr(f.TopicCount)); + Add('<b>Index count:</b> ' + IntToStr(f.Index.Count)); + Add('<b>String resource id count:</b> ' + IntToStr(f.StringResourceIDCount)); + Add('<b>Numeric resource id count:</b> ' + IntToStr(f.NumericResourceIDCount)); + Add(' '); + //Add('Dictionary contents:'); + //for i := 0 to f.DictionaryCount-1 do + // Add('[' + IntToStr(i) + '] = <' + f.DictionaryWords[i] + '>'); + end; + RichView.AddParagraph(PChar(sl.Text)); + end; + sl.Free; +end; + +procedure TMainForm.miDebugHex(Sender: TObject); +begin + Debug := not Debug; + miDebugHexInfo.Checked := Debug; + DisplayTopic(nil); +end; + +procedure TMainForm.miFileSaveTopicAsIPF(Sender: TObject); +var + F: TextFile; + T: TTopic; + dlg: TfpgFileDialog; + H: THelpFile; + i: integer; + filename: string; + imglist: TList; +begin +(* + H := THelpFile(CurrentOpenFiles[0]); + + FileName := ChangeFileExt( ExtractFileName( H.Filename ), '.ipf' ); + if not DoSaveFileDialog( FileSaveTitle, + 'IPF' + '|*.ipf', + Filename, + Settings.LastSaveDirectory, + Filename ) then + exit; + if FileExists( Filename ) then + if not DoConfirmDlg( FileSaveTitle, + ReplaceFilePromptA + + Filename + + ReplaceFilePromptB ) then + exit; + + ImageOffsets := TList.Create; + + AssignFile( F, FileName ); + Rewrite( F ); + WriteLn( F, ':userdoc.' ); + + // We can't tell if some levels of the contents were + // merged into the text of topics. So we just assume all are visible + WriteLn( F, ':docprof toc=123456.' ); + + ResourceIDs := TList.Create; + + WriteLn( F, ':title.' + H.Title ); + + for i := 0 to H.TopicCount - 1 do + begin + T := H.Topics[ i ]; + + SetProgress( i div 2, H.TopicCount , 'Saving text...' ); + + WriteLn( F, '' ); + + + if T.ContentsLevel = 0 then + begin + // perhaps it means footnote? + // Level := 1; + Write( F, ':fn id=fn' + IntToStr( i ) + '.' ); // use index as id + + T.SaveToIPF( F, ImageOffsets ); + + WriteLn( F, '' ); + WriteLn( F, ':efn.' ); + end + else + begin + Write( F, ':h' + IntToStr( T.ContentsLevel ) ); + Write( F, ' id=' + IntToStr( i ) ); // use index as id + + H.FindResourceIDsForTopic( T, ResourceIDs ); + if ResourceIDs.Count > 0 then + begin + Write( F, ' res=' + IntToStr( longint( ResourceIDs[ 0 ] ) ) ); + end; + + if not T.ShowInContents then + Write( F, ' hide' ); + + if T.ContentsGroupIndex > 0 then + Write( F, ' group=' + IntToStr( T.ContentsGroupIndex ) ); + + Write( F, '.' ); // end of header + WriteLn( F, T.Title ); + + T.SaveToIPF( F, ImageOffsets ); + end; + + + + end; + + ResourceIDs.Destroy; + + WriteLn( F, ':euserdoc.' ); + System.Close( F ); + + // Now write images + + for i := 0 to ImageOffsets.Count - 1 do + begin + ImageOffset := longint( ImageOffsets[ i ] ); + + SetProgress( i div 2 + ImageOffsets.Count div 2, + ImageOffsets.Count , + 'Saving images...' ); + + Image := H.GetImage( ImageOffset ); + + if Image <> nil then + begin + Image.SaveToFile( ExtractFilePath( Filename ) + + 'img' + + IntToStr( i ) + + '.bmp' ); + Image.Destroy; + end; + + end; + + ResetProgress; + SetStatus( 'Save complete' ); + ImageOffsets.Destroy; +*) + + + + //----------------------------- + if tvContents.Selection = nil then + Exit; //--> + + T := TTopic(tvContents.Selection.Data); + if T <> nil then + begin + dlg := TfpgFileDialog.Create(nil); + try + dlg.FileName := T.Title + '.ipf'; + if dlg.RunSaveFile then + begin + imglist := TList.Create; + AssignFile( F, dlg.FileName ); + Rewrite( F ); + T.SaveToIPF(F, imglist); + System.Close(F); + imglist.free; + dlg.Close; + dlg.Free; + end; + finally + end; + end; +end; + +procedure TMainForm.OnMRUMenuItemClick(Sender: TObject); +var + lTag: longint; + MenuItem: TfpgMenuItem; + MRUItem: TMRUItem; +begin + MenuItem := Sender as TfpgMenuItem; + lTag := MenuItem.Tag; + MRUItem := TMRUItem(Settings.MRUList[ lTag ]); + OpenFiles(MRUItem.FileNames, '', True); +end; + +procedure TMainForm.btnShowIndex(Sender: TObject); +begin + DisplaySelectedIndexTopic; +end; + +procedure TMainForm.btnGoClicked(Sender: TObject); +begin + if tvContents.Selection <> nil then + DisplayTopic(nil); +end; + +procedure TMainForm.tvContentsChange(Sender: TObject); +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + DisplayTopic(nil); +end; + +procedure TMainForm.edSearchTextKeyPress(Sender: TObject; var KeyCode: word; + var ShiftState: TShiftState; var Consumed: boolean); +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + if (KeyCode = keyReturn) or (KeyCode = keyPEnter) then + begin + Consumed := True; + btnSearch.Click; + end + else if (KeyCode = keyDown) then + begin + Consumed := True; + lbSearchResults.SetFocus; + end; +end; + +procedure TMainForm.lbSearchResultsKeyPress(Sender: TObject; var KeyCode: word; + var ShiftState: TShiftState; var Consumed: boolean); +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + if (KeyCode = keyReturn) or (KeyCode = keyPEnter) then + begin + Consumed := True; + DisplayTopic(nil); + end +end; + +procedure TMainForm.MainFormCloseQuery(Sender: TObject; var CanClose: boolean); +begin + CloseFile(True); +end; + +procedure TMainForm.PageControl1Change(Sender: TObject; NewActiveSheet: TfpgTabSheet); +begin + if NewActiveSheet = tsIndex then + begin + if not IndexLoaded then + LoadIndex; + IndexSearchEdit.SetFocus; + end + else if NewActiveSheet = tsHistory then + lbHistory.FocusItem := CurrentHistoryIndex; +end; + +procedure TMainForm.tvContentsDoubleClick(Sender: TObject; AButton: TMouseButton; + AShift: TShiftState; const AMousePos: TPoint); +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + if tvContents.Selection <> nil then + DisplayTopic(nil); +end; + +procedure TMainForm.lbIndexDoubleClick(Sender: TObject; AButton: TMouseButton; + AShift: TShiftState; const AMousePos: TPoint); +begin + InIndexSearch := True; // prevent edit.OnChange from executing too + try + IndexSearchEdit.Text := lbIndex.Items[lbIndex.FocusItem]; + DisplayTopic(nil); + finally + InIndexSearch := False; + end; +end; + +procedure TMainForm.lbSearchResultsDoubleClick(Sender: TObject; AButton: TMouseButton; + AShift: TShiftState; const AMousePos: TPoint); +begin + DisplaySelectedSearchResultTopic; +end; + +procedure TMainForm.lbHistoryDoubleClick(Sender: TObject; + AButton: TMouseButton; AShift: TShiftState; const AMousePos: TPoint); +begin + try + FHistorySelection := True; + DisplayTopic(nil); + CurrentHistoryIndex := lbHistory.FocusItem; + finally + FHistorySelection := False; + end; +end; + +procedure TMainForm.lbHistoryKeyPress(Sender: TObject; var KeyCode: word; + var ShiftState: TShiftState; var Consumed: boolean); +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + if (KeyCode = keyReturn) or (KeyCode = keyPEnter) then + begin + Consumed := True; + try + FHistorySelection := True; + DisplayTopic(nil); + CurrentHistoryIndex := lbHistory.FocusItem; + finally + FHistorySelection := False; + end; + end +end; + +procedure TMainForm.btnSearchClicked(Sender: TObject); +begin + DoSearch; +end; + +procedure TMainForm.IndexSearchEditOnChange(Sender: TObject); +var + tmpMatchIndex: longint; + tmpSearchText: string; + i: longint; +begin + if InIndexSearch then + exit; + + tmpMatchIndex := -1; + tmpSearchText := trim(IndexSearchEdit.Text); + + for i := 0 to DisplayedIndex.Count - 1 do + begin + if StrStartsWithIgnoringCase(DisplayedIndex[i], tmpSearchText) then + begin + tmpMatchIndex := i; + break; + end; + end; + + if tmpMatchIndex = -1 then + exit; + + InIndexSearch:= true; + + + if lbIndex.FocusItem <> tmpMatchIndex then + lbIndex.FocusItem := tmpMatchIndex; + + InIndexSearch:= false; +end; + +procedure TMainForm.DisplaySelectedSearchResultTopic; +var + Topic: TTopic; +begin + if lbSearchResults.FocusItem = -1 then + exit; + if lbSearchResults.Items.Objects[lbSearchResults.FocusItem] = nil then + // the "no results" place holder + exit; + Topic := lbSearchResults.Items.Objects[lbSearchResults.FocusItem] as TTopic; + DisplayTopic( Topic ); +end; + +procedure TMainForm.NavigateToHistoryIndex(AIndex: integer); +begin + try + FHistorySelection := True; + CurrentHistoryIndex := AIndex; + lbHistory.FocusItem := AIndex; + DisplayTopic(TTopic(lbHistory.Items.Objects[AIndex])); + finally + FHistorySelection := False; + end; +end; + +procedure TMainForm.UpdateLocationPanel; +var + i: integer; + s: string; + n: TfpgTreeNode; + sep: string; +begin + s := ''; + sep := ''; + n := tvContents.Selection; + while n.Parent <> nil do + begin + s := n.Parent.Text + sep + s; + n := n.Parent; + sep := ' > '; + end; + SetStatus(s); +end; + +procedure TMainForm.EnableControls; +begin + // +end; + +procedure TMainForm.ClearAllWordSequences; +var + i: longint; + FileWordSequences: TList; + HelpFile: THelpFile; +begin + if AllFilesWordSequences = nil then + exit; + + for i := 0 to AllFilesWordSequences.Count - 1 do + begin + FileWordSequences := TList(AllFilesWordSequences[ i ]); + HelpFile := THelpFile(CurrentOpenFiles[ i ]); + ClearWordSequences( FileWordSequences, HelpFile.DictionaryCount ); + FileWordSequences.Free; + end; + AllFilesWordSequences.Clear; +end; + +procedure TMainForm.DoSearch; +var + SearchResults: TList; + SearchText: string; + FileIndex: longint; + HelpFile: THelpFile; + TopicIndex: longint; + Topic: TTopic; + FileWordSequences: TList; + Query: TTextSearchQuery; +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + SearchText := Trim(edSearchText.Text); + lbSearchResults.Items.Clear; + + if SearchText = '' then + exit; + + lbSearchResults.Items.Add(rsDVSearchingMsg); + SetStatus(rsDVSearchingMsg); + + try + Query := TTextSearchQuery.Create( SearchText ); + except + on e: ESearchSyntaxError do + begin + TfpgMessageDialog.Critical( rsSearch, rsDVSearchSyntaxError + e.Message ); + exit; + end; + end; + + ClearAllWordSequences; + + SetWaitCursor; + + SearchResults := TList.Create; + + // Search open help file + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + HelpFile := THelpFile(CurrentOpenFiles[ FileIndex ]); + FileWordSequences := TList.Create; + try + SearchHelpFile( HelpFile, + Query, + SearchResults, + FileWordSequences ); + except + on E: EHelpFileException do + begin + TfpgMessageDialog.Critical(rsError , E.Message); + Query.Destroy; + ClearWaitCursor; + exit; + end; + end; + + AllFilesWordSequences.Add( FileWordSequences ); + end; + + // Sort results across all files by relevance + SearchResults.Sort( @TopicRelevanceCompare ); + + // Load topics into search results list. + lbSearchResults.Items.BeginUpdate; + lbSearchResults.Items.Clear; + + for TopicIndex := 0 to SearchResults.Count - 1 do + begin + Topic := TTopic(SearchResults[ TopicIndex ]); + lbSearchResults.Items.AddObject( Topic.Title + + ' [' + + IntToStr( Topic.SearchRelevance ) + + ']', + Topic ); + end; + + EnableControls; +// if lbSearchResults.Items.Count > 0 then + // there are some search matches, so highlight words +// ViewHighlightSearchWordsMI.Checked := true; + + lbSearchResults.FocusItem := -1; + lbSearchResults.Items.EndUpdate; + + Query.Free; + SearchResults.Free; + + if lbSearchResults.Items.Count > 0 then + begin + lbSearchResults.FocusItem := 0; + end + else + begin + lbSearchResults.Items.Add( Format(rsDVNoMatchesFound, [SearchText])); +// RefreshWindows( Windows ); // update to remove old highlights + end; + SetStatus( Format(rsDVSearchFoundMsg, [lbSearchResults.Items.Count]) + + StrInDoubleQuotes(SearchText)); + + ClearWaitCursor; + DisplaySelectedSearchResultTopic; +end; + +procedure TMainForm.SetWaitCursor; +begin + // +end; + +procedure TMainForm.ClearWaitCursor; +begin + // +end; + +procedure TMainForm.SetMainCaption; +begin + WindowTitle:= MainTitle; + fpgApplication.ProcessMessages; +end; + +procedure TMainForm.DisplayFiles(NewFiles: TList; var FirstContentsNode: TfpgTreeNode); +var + HelpFile: THelpFile; + FileIndex: longint; +begin + LogEvent(LogStartup, 'DisplayFiles' ); + // Now load the various parts of the file(s) + // into the user interface + ProgressBar.Position := 52; + SetStatus( rsDVDisplaying ); + + // Add our open files in the global filelist + { TODO -ograeme : implement global filelist support } + //for FileIndex := 0 to NewFiles.Count - 1 do + //begin + // HelpFile := NewFiles[ FileIndex ]; + // GlobalFilelist.AddFile( HelpFile.Filename, Frame.Handle ); + // // LoadNotes( HelpFile ); + // LoadBookmarks( HelpFile ); + //end; + + { TODO -ograeme : update notes display } + //UpdateNotesDisplay; + + { TODO -ograeme : bookmarks } + //BuildBookmarksMenu; + //UpdateBookmarksForm; + + ProgressBar.Position := 55; + + ContentsLoaded := false; + IndexLoaded := false; + + LoadContents( NewFiles, FirstContentsNode ); + + ProgressBar.Position := 75; + + LoadIndex; + + ProgressBar.Position := 100; + SetStatus( rsDVDone ); + + LogEvent(LogStartup, 'DisplayFiles Done' ); +end; + +procedure TMainForm.FileOpen; +var + dlg: TfpgFileDialog; +begin + dlg := TfpgFileDialog.Create(nil); + try + dlg.InitialDir := Settings.LastOpenDirectory; + dlg.WindowTitle := rsDVOpenHelpFile; + dlg.Filter := rsDVHelpFiles + ' (*.hlp, *.inf)|*.inf;*.hlp '; + // and a catch all filter + dlg.Filter := dlg.Filter + '|(' + rsAllFiles + ' (*)|*'; + + if dlg.RunOpenFile then + begin + Settings.LastOpenDirectory := ExtractFilePath(dlg.Filename); + OpenFile(dlg.Filename, '', true); + end; + { TODO -oGraeme : Add support for multiple files. } + finally + dlg.Free; + end; +end; + +function TMainForm.LoadFiles(const aFileNames: TStrings; aHelpFiles: TList): boolean; +var + HelpFile: THelpFile; + FileIndex, i: longint; + FileName: string; + FullFilePath: string; +begin + LogEvent(LogStartup, 'LoadFiles' ); + LoadingFilenameList := TStringList.Create; + +// RBRi TranslateIPFEnvironmentVars( FileNames, LoadingFilenameList ); + for i := 0 to aFileNames.Count - 1 do + LoadingFilenameList.Add(aFileNames[i]); + + LogEvent(LogStartup, 'Finding files' ); + ProgressBar.Visible := True; + + // now find full file paths, + // and also the total file size for progress display + for FileIndex := 0 to LoadingFilenameList.Count - 1 do + begin + FileName := LoadingFilenameList[ FileIndex ]; + LogEvent(LogStartup, ' File: ' + FileName ); + + // Find the help file, if possible + if Filename = OWN_HELP_MARKER then + begin + FullFilePath := GetOwnHelpFileName; + end + else + begin + FullFilePath := FindHelpFile( Filename ); + end; + + if FullFilePath <> '' then + begin + LogEvent(LogStartup, ' Full path: ' + FullFilePath ); + end + else + begin + LogEvent(LogStartup, ' File not found' ); + FullFilePath := FileName; // we'll complain later. + end; + LoadingFilenameList[ FileIndex ] := FullFilePath; + end; + + // Now actually load the files + for FileIndex := 0 to LoadingFilenameList.Count - 1 do + begin + Filename := LoadingFilenameList[ FileIndex ]; + LogEvent(LogStartup, ' Loading: ' + Filename ); + try + LoadingFileIndex := FileIndex; + + // load the file + HelpFile := THelpFile.Create( FileName ); + if Settings.FixedFontSubstitution then + HelpFile.SetupFontSubstitutes( Settings.FixedFontSubstitutes ); + + aHelpFiles.Add( HelpFile ); + + except + on E: Exception do + begin + + if E is EWindowsHelpFormatException then + begin + { TODO -ograeme -cnice to have : Implement opening Windows help } + //OpenWindowsHelp( Filename ); + end + else + begin + TfpgMessageDialog.Critical( rsDVOpenHelpFile, + Format(rsDVCouldNotOpen, [Filename]) + + ': ' + + E.Message ); + end; + + // back out of the load process + Result := false; + + DestroyListObjects( aHelpFiles ); + + LoadingFilenameList.Free; + ResetProgress; + exit; + end + end; + end; + + LoadingFilenameList.Free; + Result := true; +end; + +{ Open the file or list of files in FileNames + Set the window title if given, otherwise get it from first file } +function TMainForm.OpenFiles(const FileNames: TStrings; + const AWindowTitle: string; const DisplayFirstTopic: boolean): boolean; +var + tmpHelpFiles: TList; + FirstContentsNode: TfpgTreeNode; +begin + LogEvent(LogStartup, 'OpenFiles' ); + + //if not OKToCloseFile then + // exit; + + SetWaitCursor; + tmpHelpFiles := TList.Create; + + if not LoadFiles(FileNames, tmpHelpFiles) then + begin + ClearWaitCursor; + tmpHelpFiles.Free; + exit; + end; + + Result := true; + + lbSearchResults.Items.Clear; + lbHistory.Items.Clear; + CurrentHistoryIndex := -1; + + // Now that we have successfully loaded the new help file(s) + // close the existing one. + CloseFile; + + AssignList(tmpHelpFiles, CurrentOpenFiles ); + + ProgressBar.Position := 50; + SetStatus( rsDVDisplaying ); + + AddCurrentToMRUFiles; + + if AWindowTitle = '' then + MainTitle := THelpFile( CurrentOpenFiles[ 0 ] ).Title + else + MainTitle := AWindowTitle; + SetMainCaption; + + // Now load the various parts of the file(s) + // into the user interface + DisplayFiles( tmpHelpFiles, FirstContentsNode ); + + //if CmdLineParameters.getHelpManagerFlag then + // ShowLeftPanel := Settings.ShowLeftPanel_Help + //else + // ShowLeftPanel := Settings.ShowLeftPanel_Standalone; + + // Select first contents node if there is one + if FirstContentsNode <> nil then + begin + LogEvent(LogStartup, ' Select first node' ); + tvContents.Selection := FirstContentsNode; + tvContents.SetFocus; + end; + + ClearWaitCursor; + + ResetProgress; + +// NotebookOnPageChanged( self ); // ensure e.g. index loaded + + EnableControls; + + if DisplayFirstTopic then + begin + LogEvent(LogStartup, 'Display first topic' ); + DisplaySelectedContentsTopic; +// DisplayTopic(nil); + end; + + LogEvent(LogStartup, 'OpenFiles complete' ); +end; + +{ Open a single file } +function TMainForm.OpenFile(const AFileName: string; const AWindowTitle: string; + const DisplayFirstTopic: boolean): boolean; +var + tmpFileNames: TStringList; +begin + tmpFileNames := TStringList.Create; + ParseAndExpandFileNames(AFileName, tmpFileNames); + Result := OpenFiles( tmpFileNames, AWindowTitle, DisplayFirstTopic ); + tmpFileNames.Free; +end; + +procedure TMainForm.CloseFile(const ADestroying: boolean = False); +var + FileIndex: longint; + lHelpFile: THelpFile; +begin + tvContents.Selection := nil; + tvContents.RootNode.Clear; + + lbSearchResults.Items.Clear; + edSearchText.Clear; + lbHistory.Items.Clear; + + RichView.Clear(ADestroying); + if not ADestroying then + begin + WindowTitle := rsDVTitle + ' - ' + rsDVNoFile; + tvContents.Invalidate; + end; + + // First save notes. It's important we do this first + // since we scan all notes each time to find the ones + // belonging to this file. + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + lHelpFile := THelpFile(CurrentOpenFiles[FileIndex]); + SaveNotes( lHelpFile ); + end; + + ClearIndexComponents; + ClearAllWordSequences; + + // Now destroy help files + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + lHelpFile := THelpFile(CurrentOpenFiles[FileIndex]); + lHelpFile.Free; + end; + + CurrentOpenFiles.Clear; + ClearNotes; +end; + +procedure TMainForm.OnHelpFileLoadProgress(n, outof: integer; AMessage: string); +begin + // +end; + +procedure TMainForm.LoadNotes(AHelpFile: THelpFile); +begin +// NotesFileName:= ChangeFileExt( HelpFile.FileName, '.nte' ); + +end; + +procedure TMainForm.LoadContents(AFiles: TList; var FirstNode: TfpgTreeNode); +var + FileIndex: integer; + HelpFile: THelpFile; + TopicIndex: integer; + Node: TfpgTreeNode; + Topic: TTopic; +begin + LogEvent(LogStartup, 'Load contents outline'); + // we don't clear treeview here in case we need to load more files later. + LogEvent(LogStartup, 'Loop files'); + + FirstNode := nil; + Node := nil; + + for FileIndex:= 0 to AFiles.Count - 1 do + begin + HelpFile:= THelpFile(AFiles[ FileIndex ]); + ProfileEvent( 'File ' + IntToStr( FileIndex ) ); + TopicIndex:= 0; + ProfileEvent('TopicCount=' + IntToStr(HelpFile.TopicCount)); + while TopicIndex < HelpFile.TopicCount do + begin + Topic := HelpFile.Topics[ TopicIndex ]; + if Topic.ShowInContents then + begin + if Topic.ContentsLevel = 1 then + begin + Node := tvContents.RootNode.AppendText(Topic.Title); + Node.Data := Topic; + if FirstNode = nil then + FirstNode := Node; + inc( TopicIndex ); + end + else + begin + // child nodes + AddChildNodes( HelpFile, + Node, + Topic.ContentsLevel, + TopicIndex ); + Node := nil; + end; + end + else + begin + inc( TopicIndex ); + end; + end; + end; + LogEvent(LogStartup, ' EndUpdate' ); + + if Settings.OpenWithExpandedContents then + begin + LogEvent(LogStartup, ' Expand all contents' ); + tvContents.RootNode.Expand; + node := tvContents.RootNode.Next; + while node <> nil do + begin + node.Expand; + node := tvContents.RootNode.Next; + end; + end + else + begin + LogEvent(LogStartup, ' Expand first node' ); + // Contents has only one top level node... expand it + FirstNode.Expand; + end; + + ContentsLoaded := true; + tvContents.Invalidate; + LogEvent(LogStartup, ' Contents loaded' ); +end; + +// Gets the contents from each file. Sorts it alphabetically. +// Merges all the sorted contents and indexes together, alphabetically. +procedure TMainForm.LoadIndex; +var + tmpHelpFile: THelpFile; + tmpTextCompareResult: integer; + FileIndex: longint; + Contents: TList; + ContentsLists: TList; // of tlist + tmpIndexLists: TList; // of tstringlist + ContentsNextIndex: array[ 0..255 ] of longint; + IndexNextIndex: array[ 0..255 ] of longint; + Topic: TTopic; + ListEntry: string; + LowestEntry: string; + LastEntry: string; + tmpLowestEntryListIndex: longint; + tmpLowestEntryListType: TListType; + tmpLowestEntryTopic: TTopic; + tmpIndex: TStringList; + i: longint; +begin + if CurrentOpenFiles.Count = 0 then + Exit; + LogEvent(LogStartup, 'Create index' ); + SetWaitCursor; + LogEvent(LogStartup, ' Get/sort lists' ); + + ProgressBar.Position := 70; + SetStatus( 'Building index... ' ); + + ContentsLists := TList.Create; + tmpIndexLists := TList.Create; + + // collect the contents and index lists from the files + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + tmpHelpFile := THelpFile(CurrentOpenFiles[ FileIndex ]); + ProgressBar.Position := 70 + 10 * FileIndex div CurrentOpenFiles.Count; + + if Settings.IndexStyle in [ isAlphabetical, isFull ] then + begin + Contents := TList.Create; + Contents.Capacity := tmpHelpFile.TopicCount; // speeds up inserts + // copy [contents] topic list + for i := 0 to tmpHelpFile.TopicCount - 1 do + begin + Topic := tmpHelpFile.Topics[ i ]; + if Topic.ShowInContents then + Contents.Add( Topic ); + end; + // sort by title + Contents.Sort( @TopicTitleCompare ); + ContentsLists.Add( Contents ); + // initialise list index + ContentsNextIndex[ ContentsLists.Count - 1 ] := 0; + end; + + if Settings.IndexStyle in [ isFileOnly, isFull ] then + begin + tmpIndexLists.Add(tmpHelpFile.Index.GetLabels); + IndexNextIndex[ tmpIndexLists.Count - 1 ] := 0; + end; + end; + + // Unlike contents, we do clear the index (even if we are adding more files) + // because we need to re-merge the whole thing + DisplayedIndex.Clear; + ProgressBar.Position := 80; + + LogEvent(LogStartup, ' Merge lists' ); + LastEntry := ''; + while true do + begin + LowestEntry := ''; + tmpLowestEntryListIndex := -1; + // Find alphabetically lowest (remaining) topic + // first, look in contents lists + LogEvent(LogDebug, ' Merge contents' ); + for i := 0 to ContentsLists.Count - 1 do + begin + Contents := TList(ContentsLists.Items[i]); + if ContentsNextIndex[i] < Contents.Count then + begin + // list is not yet finished, get next entry + Topic := TTopic(Contents[ ContentsNextIndex[i] ]); + ListEntry := Topic.Title; + + if LowestEntry <> '' then + tmpTextCompareResult := CompareText( ListEntry, LowestEntry ) + else + tmpTextCompareResult := -1; + + if tmpTextCompareResult < 0 then + begin + // this index entry comes before the lowest one so far + LowestEntry := ListEntry; + tmpLowestEntryListIndex := i; + tmpLowestEntryListType := ltContents; + tmpLowestEntryTopic := Topic; + end; + end; + end; + + // look in indices + LogEvent(LogDebug, ' Merge indices' ); + for i := 0 to tmpIndexLists.Count - 1 do + begin + LogEvent(LogDebug, ' Merge indices ' + IntToStr(i) ); + tmpIndex := TStringList(tmpIndexLists.Items[i]); + if IndexNextIndex[i] < tmpIndex.Count then + begin + // list is not yet finished, get next entry + ListEntry := tmpIndex.Strings[ IndexNextIndex[i] ]; + LogEvent(LogDebug, ' indices ListEntry=' + ListEntry ); + if LowestEntry <> '' then + tmpTextCompareResult := CompareText( ListEntry, LowestEntry ) + else + tmpTextCompareResult := -1; + + if tmpTextCompareResult < 0 then + begin + // this index entry comes before the lowest one so far + LowestEntry := ListEntry; + tmpLowestEntryListIndex := i; + tmpLowestEntryListType := ltIndex; + + LogEvent(LogDebug, ' Merge indices ' + tmpIndex.Objects[ IndexNextIndex[i] ].ClassName); + tmpLowestEntryTopic := TIndexEntry( tmpIndex.Objects[ IndexNextIndex[i] ] ).getTopic; + end; + end; + end; + + if tmpLowestEntryListIndex = -1 then + // we're out + break; + + if LowestEntry <> LastEntry then + // add, if different from last + DisplayedIndex.AddObject( LowestEntry, tmpLowestEntryTopic ); + LastEntry := LowestEntry; + + if tmpLowestEntryListType = ltContents then + begin + inc( ContentsNextIndex[ tmpLowestEntryListIndex ] ); + end + else + begin + // found in one of indices. + // Check for subsequent indented strings + tmpIndex := TStringList(tmpIndexLists[ tmpLowestEntryListIndex ]); + + i := IndexNextIndex[ tmpLowestEntryListIndex ] + 1; + while i < tmpIndex.Count do + begin + ListEntry := tmpIndex.Strings[ i ]; + if ListEntry = '' then + break; + + if ListEntry[ 1 ] <> ' ' then + // not indented, stop looking + break; + + // found one, + Topic := TIndexEntry(tmpIndex.Objects[ i ]).getTopic; + DisplayedIndex.AddObject( ListEntry, Topic ); + inc( i ); + end; + IndexNextIndex[ tmpLowestEntryListIndex ] := i; + end; + end; + + ProgressBar.Position := 95; + LogEvent(LogStartup, ' Display index (count = ' + IntToStr(DisplayedIndex.Count) + ')'); + + // Now display the final index list + lbIndex.BeginUpdate; + lbIndex.Items.Assign( DisplayedIndex ); + lbIndex.EndUpdate; + + LogEvent(LogStartup, ' Tidy up' ); + tmpIndexLists.Free; + DestroyListAndObjects( ContentsLists ); + IndexLoaded := true; + + ClearWaitCursor; + + SetStatus( 'Index loaded' ); + LogEvent(LogStartup, ' Done' ); +end; + +procedure TMainForm.AddChildNodes(AHelpFile: THelpFile; AParentNode: TfpgTreeNode; + ALevel: longint; var ATopicIndex: longint); +var + Topic: TTopic; + Node: TfpgTreeNode; +begin + Node := nil; + while ATopicIndex < AHelpFile.TopicCount do + begin + Topic := AHelpFile.Topics[ ATopicIndex ]; + if Topic.ShowInContents then + begin + if Topic.ContentsLevel < ALevel then + break; + + if Topic.ContentsLevel = ALevel then + begin + Node := AParentNode.AppendText(Topic.Title); + Node.Data := Topic; + inc( ATopicIndex ); + end + else + begin + AddChildNodes( AHelpFile, + Node, + Topic.ContentsLevel, + ATopicIndex ); + Node := nil; + end + end + else + begin + inc( ATopicIndex ); + end; + end; { while } +end; + +procedure TMainForm.ClearNotes; +begin + { TODO -oGraeme : Implement me } +end; + +procedure TMainForm.ClearIndexComponents; +begin + IndexSearchEdit.Clear; + lbIndex.FocusItem := -1; + lbIndex.Items.Clear; + DisplayedIndex.Clear; + IndexLoaded := False; +end; + +procedure TMainForm.SaveNotes(AHelpFile: THelpFile); +begin + { TODO -oGraeme : Implement me } +end; + +procedure TMainForm.DisplayTopic(ATopic: TTopic); +var + lText: String; + ImageIndices: TList; + LinkIndex: longint; + Link: THelpLink; + HelpFile: THelpFile; + Topic: TTopic; + HighlightWordSequences: TList; + FileIndex: integer; +begin + if CurrentOpenFiles.Count = 0 then + Exit; + + ProfileEvent('DisplayTopic >>>>'); + if ATopic = nil then + begin + case PageControl1.ActivePageIndex of + 0: begin // TOC tab + if tvContents.Selection = nil then + begin + ShowMessage('You must select a topic first by clicking it.'); + Exit; //==> + end + else + Topic := TTopic(tvContents.Selection.Data); + ProfileEvent('Got Topic from Treeview'); + end; + 1: begin // Index tab + if lbIndex.FocusItem = -1 then + begin + ShowMessage('You must select a index first by clicking it.'); + Exit; //==> + end + else + Topic := TTopic(lbIndex.Items.Objects[lbIndex.FocusItem]); + ProfileEvent('Got Topic from Index listbox'); + end; + 2: begin // Search tab + if lbSearchResults.FocusItem = -1 then + begin + ShowMessage('You must select a search result first by clicking it.'); + Exit; //==> + end + else + Topic := TTopic(lbSearchResults.Items.Objects[lbSearchResults.FocusItem]); + ProfileEvent('Got Topic from Search Results listbox'); + end; + 4: begin // History tab + if lbHistory.FocusItem = -1 then + begin + ShowMessage('You must select a history item first by clicking it.'); + Exit; //==> + end + else + Topic := TTopic(lbHistory.Items.Objects[lbHistory.FocusItem]); + ProfileEvent('Got Topic from History listbox'); + end; + end; + end // case.. + else + Topic := ATopic; // use topic passed in as a parameter + + if Topic = nil then + raise Exception.Create('Unable to locate the Topic'); + + CurrentTopic := Topic; + + RichView.Clear; + ImageIndices := TList.Create; + ProfileEvent('Cleared memo...'); + + HelpFile := TopicFile(CurrentTopic); + if HelpFile = nil then + raise Exception.Create('Failed to get active HelpFile from Topic'); + + if HelpFile.HighlightWords <> nil then + ProfileEvent('highlightwords is ok'); + + if (AllFilesWordSequences.Count > 0) // ie we have done a search... + {and ViewHighlightSearchWordsMI.Checked} then + begin + FileIndex := CurrentOpenFiles.IndexOf( HelpFile ); + HighlightWordSequences := TList(AllFilesWordSequences[ FileIndex ]); + end + else + HighlightWordSequences := nil; + + lText := ''; + ProfileEvent('Debug show hex values = ' + BoolToStr(Debug)); + if ImageIndices <> nil then + ProfileEvent('ImageIndices initialized'); + + CurrentTopic.GetText( HighlightWordSequences, + Debug {ShowCodes}, + False {ShowWordIndices}, + lText {TopicText}, + ImageIndices, + nil {Highlights} ); + + if ImageIndices.Count > 0 then + begin + { TODO -oGraeme : We do not support images yet } +// THelpFile(CurrentTopic.HelpFile).GetImages(ImageIndices, FImages); + end; + + ImageIndices.Free; + + //writeln(lText); + //writeln('-----------------------------'); + RichView.AddText(PChar(lText)); + + tvContents.Selection := tvContents.RootNode.FindSubNode(CurrentTopic, True); + tvContents.Invalidate; + SaveNavigatePoint; + UpdateLocationPanel; +end; + +procedure TMainForm.ResetProgress; +begin + { TODO -oGraeme : implement ResetProgress } + ProgressBar.Visible := False; + ProgressBar.Position := 0; +end; + +procedure TMainForm.SetStatus(const AText: TfpgString); +begin + lblStatus.Text := AText; +end; + +function TMainForm.TranslateEnvironmentVar(AFilenames: TfpgString): TfpgString; +var + EnvironmentVarValue: string; +begin + EnvironmentVarValue := GetEnvironmentVariable(UpperCase(AFilenames)); + if EnvironmentVarValue <> '' then + Result := EnvironmentVarValue + else + Result := AFileNames; +end; + +constructor TMainForm.Create(AOwner: TComponent); +var + img: TfpgImage; +begin + inherited Create(AOwner); + fpgApplication.OnException := @MainFormException; + OnShow := @MainFormShow; + OnDestroy :=@MainFormDestroy; +// Files := TList.Create; + AllFilesWordSequences := TList.Create; + CurrentOpenFiles := TList.Create; + DisplayedIndex := TStringList.Create; + CurrentHistoryIndex := -1; + FHistorySelection := False; + { TODO -oGraeme : Make Debug a menu option } + Debug := False; + + FImages := TfpgImageList.Create; + // store up to three images per Topic - while we don't support INF images + img := CreateImage_BMP(@dv_missing, sizeof(dv_missing)); + FImages.AddImage(img); + img := CreateImage_BMP(@dv_missing, sizeof(dv_missing)); + FImages.AddImage(img); + img := CreateImage_BMP(@dv_missing, sizeof(dv_missing)); + FImages.AddImage(img); + + + // load toolbar images + fpgImages.AddMaskedBMP( + 'dv.arrowleft', @usr_arrow_left, + sizeof(usr_arrow_left), 0, 0); + + fpgImages.AddMaskedBMP( + 'dv.arrowright', @usr_arrow_right, + sizeof(usr_arrow_right), 0, 0); + + fpgImages.AddMaskedBMP( + 'dv.arrowup', @usr_arrow_up, + sizeof(usr_arrow_up), 0, 0); + + fpgImages.AddMaskedBMP( + 'dv.arrowdown', @usr_arrow_down, + sizeof(usr_arrow_down), 0, 0); + +end; + +destructor TMainForm.Destroy; +begin + RichView.Images := nil; + FImages.Free; + CurrentTopic := nil; // it was a reference only + FFileOpenRecent := nil; // it was a reference only + miOpenRecentMenu.Free; +// DestroyListAndObjects(Files); + DestroyListAndObjects(AllFilesWordSequences); + DestroyListAndObjects(CurrentOpenFiles); + inherited Destroy; +end; + +procedure TMainForm.AfterCreate; +begin + {%region 'Auto-generated GUI code' -fold} + {@VFD_BODY_BEGIN: MainForm} + Name := 'MainForm'; + SetPosition(602, 274, 654, 386); + WindowTitle := 'fpGUI Documentation Viewer'; + Hint := ''; + ShowHint := True; + WindowPosition := wpUser; + OnCloseQuery := @MainFormCloseQuery; + + bvlStatusBar := TfpgBevel.Create(self); + with bvlStatusBar do + begin + Name := 'bvlStatusBar'; + SetPosition(0, 366, 653, 20); + Anchors := [anLeft,anRight,anBottom]; + Hint := ''; + Style := bsLowered; + end; + + ProgressBar := TfpgProgressBar.Create(bvlStatusBar); + with ProgressBar do + begin + Name := 'ProgressBar'; + SetPosition(501, 2, 150, 16); + Anchors := [anRight,anBottom]; + Hint := ''; + end; + + lblStatus := TfpgLabel.Create(bvlStatusBar); + with lblStatus do + begin + Name := 'lblStatus'; + SetPosition(4, 2, 380, 16); + Anchors := [anLeft,anRight,anBottom]; + FontDesc := '#Label1'; + Hint := ''; + Text := ''; + end; + + bvlBody := TfpgBevel.Create(self); + with bvlBody do + begin + Name := 'bvlBody'; + SetPosition(0, 55, 653, 310); + Anchors := [anLeft,anRight,anTop,anBottom]; + Hint := ''; + Shape := bsSpacer; + end; + + PageControl1 := TfpgPageControl.Create(bvlBody); + with PageControl1 do + begin + Name := 'PageControl1'; + SetPosition(0, 16, 260, 276); + ActivePageIndex := 0; + Hint := ''; + TabOrder := 0; + Align := alLeft; + OnChange := @PageControl1Change; + end; + + tsContents := TfpgTabSheet.Create(PageControl1); + with tsContents do + begin + Name := 'tsContents'; + SetPosition(3, 24, 254, 249); + Text := 'Contents'; + end; + + tvContents := TfpgTreeView.Create(tsContents); + with tvContents do + begin + Name := 'tvContents'; + SetPosition(4, 32, 242, 212); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#Label1'; + Hint := ''; + ScrollWheelDelta := 60; + ShowImages := True; + TabOrder := 0; + OnChange := @tvContentsChange; + //OnDoubleClick := @tvContentsDoubleClick; + end; + + btnGo := TfpgButton.Create(tsContents); + with btnGo do + begin + Name := 'btnGo'; + SetPosition(166, 4, 80, 24); + Anchors := [anRight,anTop]; + Text := 'Go to'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 1; + OnClick := @btnGoClicked; + end; + + tsIndex := TfpgTabSheet.Create(PageControl1); + with tsIndex do + begin + Name := 'tsIndex'; + SetPosition(3, 24, 254, 249); + Text := 'Index'; + end; + + btnIndex := TfpgButton.Create(tsIndex); + with btnIndex do + begin + Name := 'btnIndex'; + SetPosition(166, 4, 80, 24); + Anchors := [anRight,anTop]; + Text := 'Go to'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 1; + OnClick := @btnShowIndex; + end; + + lbIndex := TfpgListBox.Create(tsIndex); + with lbIndex do + begin + Name := 'lbIndex'; + SetPosition(4, 32, 242, 212); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#List'; + Hint := ''; + HotTrack := False; + PopupFrame := False; + TabOrder := 1; + OnDoubleClick := @lbIndexDoubleClick; + OnKeyPress:=@lbIndexKeyPress; + end; + + IndexSearchEdit := TfpgEdit.Create(tsIndex); + with IndexSearchEdit do + begin + Name := 'IndexSearchEdit'; + SetPosition(4, 4, 152, 24); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Hint := ''; + TabOrder := 2; + Text := ''; + FontDesc := '#Edit1'; + OnChange := @IndexSearchEditOnChange; + OnKeyPress :=@IndexSearchEditKeyPress; + end; + + tsSearch := TfpgTabSheet.Create(PageControl1); + with tsSearch do + begin + Name := 'tsSearch'; + SetPosition(3, 24, 254, 301); + Text := 'Search'; + end; + + Label1 := TfpgLabel.Create(tsSearch); + with Label1 do + begin + Name := 'Label1'; + SetPosition(4, 4, 120, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Search for:'; + end; + + edSearchText := TfpgEdit.Create(tsSearch); + with edSearchText do + begin + Name := 'edSearchText'; + SetPosition(4, 20, 210, 26); + Anchors := [anLeft,anRight,anTop]; + ExtraHint := ''; + Hint := ''; + TabOrder := 1; + Text := ''; + FontDesc := '#Edit1'; + OnKeyPress :=@edSearchTextKeyPress; + end; + + Label2 := TfpgLabel.Create(tsSearch); + with Label2 do + begin + Name := 'Label2'; + SetPosition(4, 48, 172, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Criteria:'; + end; + + RadioButton1 := TfpgRadioButton.Create(tsSearch); + with RadioButton1 do + begin + Name := 'RadioButton1'; + SetPosition(12, 68, 192, 20); + FontDesc := '#Label1'; + GroupIndex := 0; + Hint := ''; + TabOrder := 3; + Text := 'This section'; + Enabled := False; + end; + + RadioButton2 := TfpgRadioButton.Create(tsSearch); + with RadioButton2 do + begin + Name := 'RadioButton2'; + SetPosition(12, 88, 192, 20); + FontDesc := '#Label1'; + GroupIndex := 0; + Hint := ''; + TabOrder := 4; + Text := 'Marked sections'; + Enabled := False; + end; + + RadioButton3 := TfpgRadioButton.Create(tsSearch); + with RadioButton3 do + begin + Name := 'RadioButton3'; + SetPosition(12, 108, 192, 20); + Checked := True; + FontDesc := '#Label1'; + GroupIndex := 0; + Hint := ''; + TabOrder := 5; + Text := 'All sections'; + Enabled := False; + end; + + RadioButton4 := TfpgRadioButton.Create(tsSearch); + with RadioButton4 do + begin + Name := 'RadioButton4'; + SetPosition(12, 128, 192, 20); + FontDesc := '#Label1'; + GroupIndex := 0; + Hint := ''; + TabOrder := 6; + Text := 'Index'; + Enabled := False; + end; + + RadioButton5 := TfpgRadioButton.Create(tsSearch); + with RadioButton5 do + begin + Name := 'RadioButton5'; + SetPosition(12, 148, 192, 20); + FontDesc := '#Label1'; + GroupIndex := 0; + Hint := ''; + TabOrder := 7; + Text := 'Marked libraries'; + Enabled := False; + end; + + RadioButton6 := TfpgRadioButton.Create(tsSearch); + with RadioButton6 do + begin + Name := 'RadioButton6'; + SetPosition(12, 168, 192, 20); + FontDesc := '#Label1'; + GroupIndex := 0; + Hint := ''; + TabOrder := 8; + Text := 'All libraries'; + Enabled := False; + end; + + lbSearchResults := TfpgListBox.Create(tsSearch); + with lbSearchResults do + begin + Name := 'lbSearchResults'; + SetPosition(4, 220, 242, 76); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#List'; + Hint := ''; + HotTrack := False; + PopupFrame := False; + TabOrder := 9; + OnDoubleClick := @lbSearchResultsDoubleClick; + OnKeyPress := @lbSearchResultsKeyPress; + end; + + Label3 := TfpgLabel.Create(tsSearch); + with Label3 do + begin + Name := 'Label3'; + SetPosition(4, 200, 196, 16); + FontDesc := '#Label1'; + Hint := ''; + Text := 'Search results:'; + end; + + btnSearch := TfpgButton.Create(tsSearch); + with btnSearch do + begin + Name := 'btnSearch'; + SetPosition(220, 20, 28, 26); + Anchors := [anRight,anTop]; + Text := 'Go'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 11; + OnClick := @btnSearchClicked; + end; + + tsNotes := TfpgTabSheet.Create(PageControl1); + with tsNotes do + begin + Name := 'tsNotes'; + SetPosition(3, 24, 254, 289); + Text := 'Notes'; + end; + + ListBox1 := TfpgListBox.Create(tsNotes); + with ListBox1 do + begin + Name := 'ListBox1'; + SetPosition(4, 32, 242, 252); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#List'; + Hint := ''; + HotTrack := False; + PopupFrame := False; + TabOrder := 0; + end; + + btnNotesAdd := TfpgButton.Create(tsNotes); + with btnNotesAdd do + begin + Name := 'btnNotesAdd'; + SetPosition(4, 4, 24, 24); + Text := ''; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.add'; + TabOrder := 1; + end; + + btnNotesEdit := TfpgButton.Create(tsNotes); + with btnNotesEdit do + begin + Name := 'btnNotesEdit'; + SetPosition(32, 4, 24, 24); + Text := ''; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.edit'; + TabOrder := 2; + end; + + btnNotesDel := TfpgButton.Create(tsNotes); + with btnNotesDel do + begin + Name := 'btnNotesDel'; + SetPosition(60, 4, 24, 24); + Text := ''; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := 0; + ImageName := 'stdimg.remove'; + TabOrder := 3; + end; + + btnNotesGoto := TfpgButton.Create(tsNotes); + with btnNotesGoto do + begin + Name := 'btnNotesGoto'; + SetPosition(166, 4, 80, 24); + Anchors := [anRight,anTop]; + Text := 'Go to'; + FontDesc := '#Label1'; + Hint := ''; + ImageName := ''; + TabOrder := 4; + end; + + tsHistory := TfpgTabSheet.Create(PageControl1); + with tsHistory do + begin + Name := 'tsHistory'; + SetPosition(3, 24, 254, 249); + Text := 'History'; + end; + + lbHistory := TfpgListBox.Create(tsHistory); + with lbHistory do + begin + Name := 'lbHistory'; + SetPosition(4, 8, 242, 236); + Anchors := [anLeft,anRight,anTop,anBottom]; + FontDesc := '#List'; + Hint := ''; + HotTrack := False; + PopupFrame := False; + TabOrder := 0; + OnDoubleClick := @lbHistoryDoubleClick; + OnKeyPress := @lbHistoryKeyPress; + end; + + Splitter1 := TfpgSplitter.Create(bvlBody); + with Splitter1 do + begin + Name := 'Splitter1'; + SetPosition(265, 120, 8, 168); + Align := alLeft; + end; + + RichView := TRichTextView.Create(bvlBody); + with RichView do + begin + Name := 'RichView'; + SetPosition(368, 192, 244, 92); + TabOrder := 2; + Align := alClient; + OnClickLink:=@RichViewClickLink; + end; + + MainMenu := TfpgMenuBar.Create(self); + with MainMenu do + begin + Name := 'MainMenu'; + SetPosition(0, 0, 654, 24); + Anchors := [anLeft,anRight,anTop]; + end; + + miFile := TfpgPopupMenu.Create(self); + with miFile do + begin + Name := 'miFile'; + SetPosition(292, 96, 132, 20); + AddMenuItem('Open...', '', @miFileOpenClicked); + AddMenuItem('Open Special...', '', @miFileOpenSpecialClicked); + AddMenuItem('Save current Topic to IPF...', '', @miFileSaveTopicAsIPF); + AddMenuItem('Close', '', @miFileCloseClicked); + AddMenuitem('-', '', nil); + FFileOpenRecent := AddMenuItem('Open Recent...', '', nil); + AddMenuitem('-', '', nil); + AddMenuItem('Quit', '', @miFileQuitClicked); + end; + + miSettings := TfpgPopupMenu.Create(self); + with miSettings do + begin + Name := 'miSettings'; + SetPosition(292, 120, 132, 20); + AddMenuItem('Options...', '', @miConfigureClicked); + end; + + miBookmarks := TfpgPopupMenu.Create(self); + with miBookmarks do + begin + Name := 'miBookmarks'; + SetPosition(292, 144, 132, 20); + AddMenuItem('Add..', '', nil); + AddMenuItem('Show', '', nil); + end; + + miHelp := TfpgPopupMenu.Create(self); + with miHelp do + begin + Name := 'miHelp'; + SetPosition(292, 168, 132, 20); + AddMenuItem('Contents...', '', nil); + AddMenuItem('Help using help', '', nil); + AddMenuItem('Command line parameters', '', @miHelpCmdLineParams); + AddMenuItem('-', '', nil); + AddMenuItem('Show help file header info', '', @miDebugHeader); + AddMenuItem('-', '', nil); + AddMenuItem('About fpGUI Toolkit...', '', @miHelpAboutFPGui); + AddMenuItem('Product Information...', '', @miHelpProdInfoClicked); + end; + + miDebug := TfpgPopupMenu.Create(self); + with miDebug do + begin + Name := 'miDebug'; + SetPosition(292, 192, 132, 20); + miDebugHexInfo := AddMenuItem('Toggle Hex INF Values in Contents', '', @miDebugHex); + end; + + ToolBar := TfpgBevel.Create(self); + with ToolBar do + begin + Name := 'ToolBar'; + SetPosition(0, 25, 654, 28); + Anchors := [anLeft,anRight,anTop]; + Hint := ''; + Style := bsLowered; + Shape := bsBottomLine; + end; + + btnOpen := TfpgButton.Create(ToolBar); + with btnOpen do + begin + Name := 'btnOpen'; + SetPosition(30, 1, 24, 24); + Text := ''; + Flat := True; + FontDesc := '#Label1'; + Hint := 'Open a new help file.'; + ImageMargin := -1; + ImageName := 'stdimg.open'; + ImageSpacing := 0; + TabOrder := 0; + OnClick := @miFileOpenClicked; + Focusable := False; + end; + + btnBack := TfpgButton.Create(ToolBar); + with btnBack do + begin + Name := 'btnBack'; + SetPosition(70, 1, 32, 24); + Text := '<'; + Flat := True; + FontDesc := '#Label1'; + Hint := 'Previous history item.'; + ImageMargin := -1; + ImageName := 'dv.arrowleft'; + ImageSpacing := 0; + TabOrder := 2; + Focusable := False; + OnClick := @btnBackHistClick; + end; + + btnFwd := TfpgButton.Create(ToolBar); + with btnFwd do + begin + Name := 'btnFwd'; + SetPosition(104, 1, 32, 24); + Text := '>'; + Flat := True; + FontDesc := '#Label1'; + Hint := 'Next history item.'; + ImageMargin := -1; + ImageName := 'dv.arrowright'; + ImageSpacing := 0; + TabOrder := 3; + Focusable := False; + OnClick := @btnFwdHistClick; + end; + + btnPrev := TfpgButton.Create(ToolBar); + with btnPrev do + begin + Name := 'btnPrev'; + SetPosition(138, 1, 32, 24); + Text := 'prev'; + Flat := True; + FontDesc := '#Label1'; + Hint := 'Previous Topic.'; + ImageMargin := -1; + ImageName := 'dv.arrowup'; + ImageSpacing := 0; + TabOrder := 4; + Focusable := False; + OnClick := @btnPrevClick; + end; + + btnNext := TfpgButton.Create(ToolBar); + with btnNext do + begin + Name := 'btnNext'; + SetPosition(172, 1, 32, 24); + Text := 'next'; + Flat := True; + FontDesc := '#Label1'; + Hint := 'Next Topic.'; + ImageMargin := -1; + ImageName := 'dv.arrowdown'; + ImageSpacing := 0; + TabOrder := 5; + Focusable := False; + OnClick :=@btnNextClick; + end; + + btnHelp := TfpgButton.Create(ToolBar); + with btnHelp do + begin + Name := 'btnHelp'; + SetPosition(218, 1, 24, 24); + Text := ''; + Flat := True; + FontDesc := '#Label1'; + Hint := 'Display Product Information.'; + ImageMargin := -1; + ImageName := 'stdimg.help'; + ImageSpacing := 0; + TabOrder := 6; + Focusable := False; + OnClick := @miHelpProdInfoClicked; + end; + + btnQuit := TfpgButton.Create(ToolBar); + with btnQuit do + begin + Name := 'btnQuit'; + SetPosition(4, 1, 24, 24); + Text := ''; + Flat := True; + FontDesc := '#Label1'; + Hint := ''; + ImageMargin := -1; + ImageName := 'stdimg.quit'; + ImageSpacing := 0; + TabOrder := 8; + OnClick := @miFileQuitClicked; + Focusable := False; + end; + + Bevel1 := TfpgBevel.Create(ToolBar); + with Bevel1 do + begin + Name := 'Bevel1'; + SetPosition(61, 0, 6, 24); + Hint := ''; + Style := bsLowered; + Shape := bsLeftLine; + end; + + Bevel2 := TfpgBevel.Create(ToolBar); + with Bevel2 do + begin + Name := 'Bevel2'; + SetPosition(210, 0, 6, 24); + Hint := ''; + Style := bsLowered; + Shape := bsLeftLine; + end; + + {@VFD_BODY_END: MainForm} + {%endregion} + + // remove toolbar button text + btnBack.Text := ''; + btnFwd.Text := ''; + btnNext.Text := ''; + btnPrev.Text := ''; + + miOpenRecentMenu := TfpgPopupMenu.Create(nil); + with miOpenRecentMenu do + begin + Name := 'miOpenRecentMenu'; + SetPosition(309, 52, 132, 20); + end; + + // hook up the sub-menus. + MainMenu.AddMenuItem('&File', nil).SubMenu := miFile; + MainMenu.AddMenuItem('&Settings', nil).SubMenu := miSettings; + MainMenu.AddMenuItem('&Bookmarks', nil).SubMenu := miBookmarks; + MainMenu.AddMenuItem('&Help', nil).SubMenu := miHelp; + MainMenu.AddMenuItem('&Debug', nil).SubMenu := miDebug; + FFileOpenRecent.SubMenu := miOpenRecentMenu; + + // correct default visible tabsheet + PageControl1.ActivePageIndex := 0; +end; + +procedure TMainForm.RefreshFontSubstitutions; +var + FileIndex: longint; + HelpFile: THelpFile; +begin + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + HelpFile := THelpFile(CurrentOpenFiles[ FileIndex ]); + + if Settings.FixedFontSubstitution then + HelpFile.SetupFontSubstitutes( Settings.FixedFontSubstitutes ) + else + HelpFile.SetupFontSubstitutes( '' ); + end; +end; + +procedure TMainForm.DisplaySelectedContentsTopic; +var + Topic: TTopic; +Begin + if tvContents.Selection = nil then + Exit; + Topic := TTopic(tvContents.Selection.Data); + DisplayTopic(Topic); +End; + +procedure TMainForm.DisplaySelectedIndexTopic; +var + Topic: TTopic; +begin + if lbIndex.FocusItem = -1 then + exit; + Topic := DisplayedIndex.Objects[ lbIndex.FocusItem ] as TTopic; + DisplayTopic(Topic); +end; + +procedure TMainForm.ProcessCommandLineParams; +var + showtopic: boolean; + t: TTopic; +begin + if ParamCount > 0 then + begin + if gCommandLineParams.IsParam('h') then + begin + ShowCmdLineParamHelp; + Exit; //==> + end + else if gCommandLineParams.IsParam('debuglog') then + // do nothing + else + begin +//writeln('DEBUG: TMainForm.ProcessCommandLineParams - Open file...'); + showtopic := not gCommandLineParams.IsParam('k'); + OpenFile(ParamStr(1), '', showtopic); + end; + end; + + // now process all other parameters + if gCommandLineParams.IsParam('k') then + begin +//writeln('DEBUG: TMainForm.ProcessCommandLineParams - Keyword Search string'); + { Search for a string } + edSearchText.Text := gCommandLineParams.GetParam('k'); + PageControl1.ActivePage := tsSearch; + btnSearch.Click; + end + else if gCommandLineParams.IsParam('n') then + begin + { Display topic with numeric topic id } +//writeln('DEBUG: TMainForm.ProcessCommandLineParams - Display numeric topic id'); + t := FindTopicByResourceID(StrToInt(gCommandLineParams.GetParam('n'))); +//if not Assigned(t) then +// writeln(Format('Failed to find topic <%s>', [gCommandLineParams.GetParam('n')])); + DisplayTopic(t); + end + else if gCommandLineParams.IsParam('s') then + begin +//writeln('DEBUG: TMainForm.ProcessCommandLineParams - display string topic id'); + { Display topic with string topic id } + t := FindTopicByName(gCommandLineParams.GetParam('s')); +//if not Assigned(t) then +// writeln(Format('Failed to find topic <%s>', [gCommandLineParams.GetParam('k')])); + DisplayTopic(t); + end; +end; + +procedure TMainForm.SaveNavigatePoint; +begin + // if we selected an item from history listbox, don't record that save point + if FHistorySelection then + Exit; + + // delete rest of history + while CurrentHistoryIndex < lbHistory.Items.Count-1 do + lbHistory.Items.Delete(CurrentHistoryIndex + 1); + + if CurrentTopic <> nil then + lbHistory.Items.AddObject(CurrentTopic.Title, CurrentTopic); + + inc(CurrentHistoryIndex); +end; + +procedure TMainForm.ShowCmdLineParamHelp; +const + le = LineEnding; +var + s: string; +begin + s := '<font "Arial" 12><b>' + cLongName + '</b></font>' + le + + cVersion + le + le + + 'Supported command line parameters:' + le + le + + '<tt>' + + ' <<filename> Load the help file <<filename>' + le + + ' -h Show this help' + le + + ' -k <<text> Search for keyword <<text> in open help files' + le + + ' -n <<id> Open Topic with numeric ID equal to <<id>' + le + + ' -s <<id> Open Topic with string ID equal to <<id>' + le + + ' -debuglog <<filename> Log information to a file' + le + + '</tt>' + ; + RichView.AddText(PChar(s)); +end; + +// Find the target topic for the given link +function TMainForm.FindTopicForLink(Link: THelpLink): TTopic; +var + HelpFile: THelpFile; +begin + HelpFile := Link.HelpFile as THelpFile; + if Link is TFootnoteHelpLink then + begin + Result := HelpFile.Topics[ TFootnoteHelpLink( Link ).TopicIndex ]; + end + else if Link is TInternalHelpLink then + begin + Result := HelpFile.Topics[ TInternalHelpLink( Link ).TopicIndex ]; + end + else if Link is THelpLinkByResourceID then + begin + Result := FindTopicByResourceID( THelpLinkByResourceID( Link ).ResourceID ); + end +end; + +// Find topic specified by numeric resource ID, in all open files +function TMainForm.FindTopicByResourceID(ID: word): TTopic; +var + FileIndex: longint; + HelpFile: THelpFile; +begin + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + HelpFile := THelpFile(CurrentOpenFiles[ FileIndex ]); + + Result := HelpFile.FindTopicByResourceID( ID ); + if Result <> nil then + // found + exit; + end; + + // not found. + Result := nil; +end; + +function TMainForm.FindTopicByName(const AName: string): TTopic; +var + FileIndex: longint; + HelpFile: THelpFile; +begin + Result := nil; + for FileIndex := 0 to CurrentOpenFiles.Count - 1 do + begin + HelpFile := THelpFile(CurrentOpenFiles[ FileIndex ]); + Result := HelpFile.FindTopicByLocalName(AName); + if Result <> nil then + // found + exit; //==> + end; + // not found. + Result := nil; +end; + +// Add the current list of open files as +// a Most Recently Used entry +procedure TMainForm.AddCurrentToMRUFiles; +var + tmpFilenames: TStringList; + i: longint; + tmpHelpFile: THelpFile; +begin + tmpFilenames := TStringList.Create; + + if CurrentOpenFiles.Count > 0 then + begin + for i := 0 to CurrentOpenFiles.Count - 1 do + begin + tmpHelpFile := THelpFile(CurrentOpenFiles[ i ]); + tmpFilenames.Add(tmpHelpFile.Filename); + end; + + // update most-recently-used file list + tmpHelpFile := THelpFile(CurrentOpenFiles[ 0 ]); + AddToMRUList(tmpHelpFile.Title, tmpFilenames); + end; + + // recreate menu + CreateMRUMenuItems; + + tmpFilenames.Destroy; +end; + +procedure TMainForm.CreateMRUMenuItems; +var + MenuItem: TfpgMenuItem; + i: integer; + FileName: TfpgString; + FileNameIndex: longint; + MRUText: TfpgString; + MRUItem: TMRUItem; +begin + FFileOpenRecent.SubMenu := nil; + miOpenRecentMenu.Free; + miOpenRecentMenu := TfpgPopupMenu.Create(nil); + FFileOpenRecent.SubMenu := miOpenRecentMenu; + + // Add items for the MRU files + for i:= 0 to Settings.MRUList.Count -1 do + begin + MRUItem := TMRUItem(Settings.MRUList[ i ]); + MenuItem := miOpenRecentMenu.AddMenuItem('?', '', @OnMRUMenuItemClick); +// MenuItem.Name := 'MRUItem' + IntToStr( i ) + 'MI'; + + MRUText := MRUItem.Title; + if Trim( MRUText ) = '' then + begin + // Take the filenames, less path, as caption... + MRUText := ''; + for FileNameIndex := 0 to MRUItem.Filenames.Count - 1 do + begin + FileName := MRUItem.Filenames[ FileNameIndex ]; + FileName := fpgExtractFileName(FileName); + FileName := ChangeFileExt( FileName, '' );// remove extension + + if FileNameIndex > 0 then + begin + MRUText := MRUText + HELP_FILE_DELIMITER; + end; + MRUText := MRUText + FileName; + + // stop after 50 chars + if UTF8Length( MRUText ) > 50 then + begin + MRUText := MRUText + HELP_FILE_DELIMITER + ' ...'; + break; + end; + end; + end; + + MenuItem.Text := '&' + + IntToStr( i + 1 ) + + '. ' + + MRUText; + if MRUItem.Filenames.Count = 1 then + MenuItem.Hint := MRUItem.Filenames[ 0 ] + else + MenuItem.Hint := MRUItem.Title + + ' (' + + IntToStr( MRUItem.Filenames.Count ) + + ' ' + + rsfiles + + ')'; + + MenuItem.Tag := i; + end; +end; + + +end. diff --git a/docview/src/missing.inc b/docview/src/missing.inc new file mode 100644 index 00000000..8be26a60 --- /dev/null +++ b/docview/src/missing.inc @@ -0,0 +1,730 @@ + +Const + dv_missing : Array[0..12341] of byte = ( + 66, 77, 54, 48, 0, 0, 0, 0, 0, 0, 54, 0, 0, 0, 40, 0, 0, + 0, 64, 0, 0, 0, 64, 0, 0, 0, 1, 0, 24, 0, 0, 0, 0, 0, + 0, 48, 0, 0, 19, 11, 0, 0, 19, 11, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255, + 255,255,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255, + 255,255, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255,255,255, + 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, + 255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255, + 255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255,255, + 255,255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255,255,255,255,255,255,255, 0, 0, 0,255,255, + 255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0,255,255,255,255,255,255, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255,255,255,255,255,255,255, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255,255,255, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0,255,255, + 255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255,255, + 255,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255, + 255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255, + 255,255, 0, 0, 0,255,255,255,255,255,255,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255, + 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255, + 255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,255,255,255, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0,255,255,255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, + 255,255,255,255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ); diff --git a/docview/src/nvNullObjects.pas b/docview/src/nvNullObjects.pas new file mode 100644 index 00000000..c5e52a22 --- /dev/null +++ b/docview/src/nvNullObjects.pas @@ -0,0 +1,39 @@ +unit nvNullObjects; + +{$mode objfpc}{$H+} + +interface + +uses + contnrs, Classes, SysUtils, fpg_main; + +type + EHelpBitmapException = class(Exception); + + THelpBitmap = class(TfpgImage) + public + constructor CreateFromHelpFile( FileHandle: TFileStream; Offset: longint ); + procedure LoadFromResourceName(const AName: string); + end; + + + +implementation + + + +{ THelpBitmap } + +constructor THelpBitmap.CreateFromHelpFile(FileHandle: TFileStream; Offset: longint); +begin + inherited Create; +end; + +procedure THelpBitmap.LoadFromResourceName(const AName: string); +begin + // +end; + + +end. + diff --git a/docview/src/nvUtilities.pas b/docview/src/nvUtilities.pas new file mode 100644 index 00000000..568fbc19 --- /dev/null +++ b/docview/src/nvUtilities.pas @@ -0,0 +1,404 @@ +unit nvUtilities; + +{$mode objfpc}{$H+} + +// disable to remove debugging output +{.$Define DEBUG} + +interface + +uses + Classes, SysUtils, fpg_base; + +const + { TODO -oGraeme : Should this change to LineEnding (platfrom dependant) } + EndLine= chr(13)+chr(10); + TwoEndLines= chr(13)+chr(10)+chr(13)+chr(10); + Quote = ''''; + DoubleQuote = '"'; + + // -- Logging -- +type + LogAspect = ( LogStartup, + LogShutdown, + LogSettings, + LogI18n, + LogParse, + LogDisplay, + LogSearch, + LogNHM, + LogViewStub, + LogObjConstDest, + LogDebug + ); + LogAspects = SET OF LogAspect; + +procedure LogEvent(const aLogAspect: LogAspect; const anEventDescription: String); + +// Removes and returns the first value in a separated +// value list (removes quotes if found) +Function ExtractNextValue(var S: string; const Separator: string ): string; + +Function ExtractNextValueNoTrim(var S: string; const Separator: string ): string; + +function AllocateMemory( const Size: ValUInt ): pointer; +procedure DeallocateMemory( Var P: pointer ); + +// Alias method which is the same as Move() but with less confusing name +procedure MemCopy(const src; var dest; size: SizeInt); +procedure FillMem( Dest: pointer; Size: longint; Data: Byte ); +// Allows for debug output and quite disable of output +procedure ProfileEvent(const AString: string); +// Return AFilename's size in bytes +function GetFileSize(const AFilename: string): integer; + +function IsDigit(const AChar: TfpgChar): boolean; +function IsAlpha(const AChar: TfpgChar): boolean; +function Between( const Value: longint; const Limit1: longint; const Limit2: longint ): boolean; + + +operator = (ARect: TRect; BRect: TRect): boolean; + +// Destroy the objects stored in List and clear the list. +procedure ClearListAndObjects( List: TList ); +// Destroy the objects stored in the list and then destroy the list itself +// And set the reference to nil +procedure DestroyListAndObjects( Var List: TList ); +// Destroy the objects stored in the list. +// You probably want to use one of the two functions above. +procedure DestroyListObjects( List: TList ); + +procedure AddList( Source, Dest: TList ); +procedure AssignList( Source, Dest: TList ); + +procedure ListFilesInDirectory(const aDirectory: String; const aFilter: String; const aWithDirectoryFlag: boolean; var aList: TStrings); + +// add all file name parts of aFileNameString to the aResult +// check for containing environment vars +// and include all help files if the environment var points +// to a directory +procedure ParseAndExpandFileNames(const aFileNameString: String; aResult: TStrings); + + + +var + startTime : Cardinal; + lastTime : Cardinal; + activeLogAspects : LogAspects; + infoMessage1 : String; + infoMessage2 : String; + + +implementation + +uses + fpg_utils + ,fpg_main + ,ACLStringUtility + ,dvconstants + ; + + Function GetAspectPrefix(const aLogAspect: LogAspect): String; + Begin + Case aLogAspect of + LogStartup : result := 'Startup'; + LogShutdown : result := 'Start'; + LogSettings : result := 'Settings'; + LogI18n : result := 'I18n'; + LogParse : result := 'Parse'; + LogDisplay : result := 'Display'; + LogSearch : result := 'Search'; + LogNHM : result := 'NewHelpManager'; + LogViewStub : result := 'ViewStub'; + LogObjConstDest : result := 'ObjConstDest'; + LogDebug : result := 'Debug'; + else result := 'Unknown'; + end; + End; + + + Procedure SetLogAspects(const aCommaSeparatedListOfAspectNames : String); + Var + tmpAspects : TStrings; + i : Integer; + Begin + tmpAspects := TStringList.Create; + StrExtractStrings(tmpAspects, aCommaSeparatedListOfAspectNames, [','], #0); + + for i:=0 to tmpAspects.count-1 do + begin + if tmpAspects[i] = 'LogStartup' then activeLogAspects := activeLogAspects + [ LogStartup ]; + if tmpAspects[i] = 'LogShutdown' then activeLogAspects := activeLogAspects + [ LogShutdown ]; + if tmpAspects[i] = 'LogSettings' then activeLogAspects := activeLogAspects + [ LogSettings ]; + if tmpAspects[i] = 'LogI18n' then activeLogAspects := activeLogAspects + [ LogI18n ]; + if tmpAspects[i] = 'LogParse' then activeLogAspects := activeLogAspects + [ LogParse ]; + if tmpAspects[i] = 'LogDisplay' then activeLogAspects := activeLogAspects + [ LogDisplay ]; + if tmpAspects[i] = 'LogSearch' then activeLogAspects := activeLogAspects + [ LogSearch ]; + if tmpAspects[i] = 'LogNHM' then activeLogAspects := activeLogAspects + [ LogNHM ]; + if tmpAspects[i] = 'LogViewStub' then activeLogAspects := activeLogAspects + [ LogViewStub ]; + if tmpAspects[i] = 'LogObjConstDest' then activeLogAspects := activeLogAspects + [ LogObjConstDest ]; + if tmpAspects[i] = 'LogDebug' then activeLogAspects := activeLogAspects + [ LogDebug ]; + end; + + tmpAspects.Destroy; + End; + +procedure LogEvent(const aLogAspect: LogAspect; const anEventDescription: String); +var + tmpMessage: String; +begin + if (aLogAspect IN activeLogAspects) then + begin + tmpMessage := 'Log[' + GetAspectPrefix(aLogAspect) + '] ' + anEventDescription; + debugln(tmpMessage); + end; +end; + +Function ExtractNextValue( var S: string; + const Separator: string ): string; +begin + Result := ExtractNextValueNoTrim( S, Separator ); + Result := trim( Result ); + + // Remove quotes if present + if Result <> '' then + if Result[ 1 ] = DoubleQuote then + Delete( Result, 1, 1 ); + + if Result <> '' then + if Result[ length( Result ) ] = DoubleQuote then + Delete( Result, length( Result ), 1 ); +end; + +Function ExtractNextValueNoTrim( var S: string; + const Separator: string ): string; +Var + SeparatorPos: integer; +Begin + SeparatorPos := Pos( Separator, S ); + if SeparatorPos > 0 then + begin + Result := Copy( S, 1, SeparatorPos-1 ); + Delete( S, 1, SeparatorPos + length( Separator ) - 1 ); + end + else + begin + Result := S; + S := ''; + end; +end; + +function AllocateMemory( const Size: ValUInt ): pointer; +begin + GetMem( Result, size + sizeof( Size ) ); + PtrUInt(Result^) := Size; + inc( Result, sizeof( Size ) ); +end; + +procedure DeallocateMemory( Var P: pointer ); +var + Size: ValUInt; +begin + if P = nil then + exit; + + dec( P, sizeof( size ) ); + Size := ValUInt(P^); + FreeMem( P, Size + sizeof( Size ) ); + P := nil; +end; + +procedure MemCopy(const src; var dest; size: SizeInt); +begin + Move(src, dest, size); +end; + +procedure FillMem( Dest: pointer; Size: longint; Data: Byte ); +begin + FillChar( Dest^, Size, Data ); +end; + +procedure ProfileEvent(const AString: string); +begin + {$IFDEF DEBUG} + writeln('DEBUG: ', AString); + {$ENDIF} +end; + +function GetFileSize(const AFilename: string): integer; +var + f: File; +begin + Result := 0; + {$i-} + FileMode := 0; // read-only + Assign(f, fpgToOSEncoding(AFileName)); + Reset(f); + {$i+} + Result := FileSize(f); + CloseFile(f); +end; + +function IsDigit(const AChar: TfpgChar): boolean; +begin + { TODO -oGraeme -cunicode : Not utf-8 compliant. } + Result := ( AChar>='0' ) and ( AChar<='9' ); + //Result := TCharacter.IsDigit(AChar); +end; + +function IsAlpha(const AChar: TfpgChar): boolean; +var + UppercaseC: TfpgChar; +Begin + { TODO -oGraeme -cunicode : Not utf-8 compliant. } + UppercaseC := UpperCase( AChar ); + Result := ( UppercaseC >= 'A' ) and ( UppercaseC <= 'Z' ); + //Result := TCharacter.IsLetter(AChar); +end; + +function Between( const Value: longint; const Limit1: longint; const Limit2: longint ): boolean; +begin + if Limit1 < Limit2 then + Result:= ( Value >= Limit1 ) and ( Value <= Limit2 ) + else + Result:= ( Value >= Limit2 ) and ( Value <= Limit1 ) +end; + +operator = (ARect: TRect; BRect: TRect): boolean; +begin + result := (ARect.Top = BRect.Top) and + (ARect.Left = BRect.Left) and + (ARect.Bottom = BRect.Bottom) and + (ARect.Right = BRect.Right); +end; + +// Destroy the objects stored in List +// and clear the list. +Procedure ClearListAndObjects( List: TList ); +begin + DestroyListObjects( List ); + List.Clear; +end; + +// Destroy the objects stored in the list +// and then destroy the list itself. +Procedure DestroyListAndObjects( Var List: TList ); +begin + if not Assigned( List ) then + exit; + + DestroyListObjects( List ); + List.Free; + List := nil; +end; + +Procedure DestroyListObjects( List: TList ); +var + Index: longint; +begin + for Index := 0 to List.Count - 1 do + begin + if List[ Index ] <> nil then + begin + TObject( List[ Index ] ).Free; + List[ Index ] := nil; + end; + end; +end; + +Procedure AddList( Source, Dest: TList ); +var + i: longint; +begin + // expand the destination list to what's required + Dest.Capacity := Dest.Capacity + Source.Capacity; + for i:= 0 to Source.Count - 1 do + Dest.Add( Source[ i ] ); +end; + +Procedure AssignList( Source, Dest: TList ); +begin + Dest.Clear; + AddList( Source, Dest ); +end; + +procedure ListFilesInDirectory(const aDirectory: String; const aFilter: String; + const aWithDirectoryFlag: boolean; var aList: TStrings); +var + tmpRC: longint; + tmpSearchResults: TSearchRec; + tmpMask: String; + tmpFilterParts : TStrings; + tmpDirectory: String; + i: integer; +begin + tmpFilterParts := TStringList.Create; + + StrExtractStrings(tmpFilterParts, aFilter, [PathSeparator], #0); + + for i:=0 to tmpFilterParts.count-1 do + begin + tmpMask := tmpFilterParts[i]; + tmpDirectory := IncludeTrailingPathDelimiter(aDirectory); + tmpRC := fpgFindFirst(tmpDirectory + tmpMask, faAnyFile, tmpSearchResults); + + while tmpRC = 0 do + begin + if (tmpSearchResults.Attr and faDirectory) = 0 then + begin + if (aWithDirectoryFlag) then + aList.Add(tmpDirectory + tmpSearchResults.Name) + else + aList.Add(tmpSearchResults.Name); + end; + + tmpRC := fpgFindNext(tmpSearchResults); + end; + + FindClose(tmpSearchResults); + end; + tmpFilterParts.Destroy; +end; + +procedure ParseAndExpandFileNames(const aFileNameString: String; aResult: TStrings); +var + i: longint; + tmpFileNamesList: TStrings; + tmpItem: String; + tmpEnvironmentVarValue: string; +begin + LogEvent(LogDebug, 'ParseAndExpandFileNames "' + aFileNameString + '"'); + tmpFileNamesList := TStringList.Create; + + StrExtractStrings(tmpFileNamesList, aFileNameString, [HELP_FILE_DELIMITER, PathSeparator], #0); + for i := 0 to tmpFileNamesList.Count - 1 do + begin + tmpItem := tmpFileNamesList[i]; + + // is this a environment var + tmpEnvironmentVarValue := GetEnvironmentVariable(tmpItem); + if tmpEnvironmentVarValue <> '' then // environment var exists + begin + LogEvent(LogStartup, ' Environment var found; translated to: "' + tmpEnvironmentVarValue + '"'); + ParseAndExpandFileNames(tmpEnvironmentVarValue, aResult); + end + else if fpgDirectoryExists(tmpItem) then + begin + ListFilesInDirectory(tmpItem, '*' + INF_FILE_EXTENSION, true, aResult); + end + else + begin + aResult.Add(tmpItem); + end; + end; + + tmpFileNamesList.Free; +end; + +{$IFDEF DEBUG} +initialization + SetLogAspects('LogDebug,LogStartup'); +{$ENDIF} + +end. + diff --git a/docview/src/units/.gitignore b/docview/src/units/.gitignore new file mode 100644 index 00000000..72e8ffc0 --- /dev/null +++ b/docview/src/units/.gitignore @@ -0,0 +1 @@ +* |