home *** CD-ROM | disk | FTP | other *** search
-
- {*****************************************************************************}
- { }
- { QDB v2.11 Visual Components for Delphi 1, 2, & 3 }
- { }
- { Copyright (c) 1995, 1996, 1997, 1998 Robert R. Marsh, S.J. }
- { & the British Province of the Society of Jesus }
- { }
- { This source code may *not* be redistributed }
- { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ }
- { }
- { If you like QDB and find yourself using it please consider }
- { making a donation to your favorite charity. I would also be }
- { pleased if you would acknowledge QDB in any projects that }
- { make use of it. }
- { }
- { QDB is supplied as is. The author disclaims all warranties, }
- { expressed or implied, including, without limitation, the }
- { warranties of merchantability and of fitness for any purpose. }
- { The author assumes no liability for damages, direct or }
- { consequential, which may result from the use of QDB. }
- { }
- { rrm@sprynet.com }
- { http://home.sprynet.com/sprynet/rrm }
- { }
- {*****************************************************************************}
-
- (*
- Portions of the code are based on the work of others:
-
- TQDBNavigator is based on TDBNavigator Copyright (c) 1995-1997
- Borland International. All Rights Reserved.
-
- The vertical orientation of TQDBNavigator is modeled after
- DBVNav97 by Bourmad Mehdi(Mehdi.Bourmad@de.edfgdf.fr).
-
- The Secure Hash Algorithm (SHA-1) used in the password routines is based
- on the implementation by Koos Lodewijkx (J.P.Lodewijkx@inter.nl.net).
-
- The grep-style pattern matching routine is based on the code of Gerald Nunn
- and comes from GEXperts his excellent suite of Delphi add-ins at
- http://www.amano-blick.com/~gnunn/GExperts.htm.
-
- The buffered stream class was based on *someone's* source but the code
- has no name attached, I can't remember where I downloaded it, and no
- amount of web-searching has turned it up. If you recognize it, do
- please let me know, so that I can properly acknowledge the author's
- work.
-
- Thanks to Bob Stammers for fixing a problem when TQDBNavigator is
- created and destroyed at run-time.
-
- *)
-
- (*
- Watch out for compiler warnings after try ... except blocks. The compiler
- doesn't know that the various error functions raise exceptions of their
- own which prevents the following code from ever being executed uninitialized.
- *)
-
- unit QDB;
-
- interface
-
- uses
- {$IFDEF WIN32}Windows, {$ELSE}WinTypes, WinProcs, {$ENDIF}
- SysUtils, Classes, Messages, Controls, Forms,
- ExtCtrls, Buttons, Graphics;
-
- type
- string40 = string[40];
- string05 = string[5];
-
- const
- FileVersion: string05 = '2.11';
- AuthorInfo: string40 = 'Robert R. Marsh, SJ -- rrm@sprynet.com';
-
- var
- QDBTempFileLocation: string;
-
- type
- { basic types that your application needs to know about }
- TKey = string[255]; { QDB key }
- TItemIndex = longint; { pointer into index }
- TDataIndex = longint; { pointer into item of data }
-
- type
- { other simple types used internally }
- TQDBFileName = string;
- TFileHandle = integer;
- TFilePos = longint; { pointer into disk file }
-
- type
- { event used to signal progress of lengthy process }
- TPercentage = 0..100;
- TProgressOrigin = (prStart, prFinish, prSave, prPack, prKeyList, prCompress);
- TProgressEvent = procedure(Sender: TObject;
- Percent: TPercentage;
- Kind: TProgressOrigin) of object;
-
- type
- { event used to get confirmation from user }
- TConfirmEvent = procedure(Sender: TObject; var OK: boolean) of object;
-
- type
- { event used to give warning to the user }
- TWarningEvent = TNotifyEvent;
-
- type
- { event used to elicit password }
- TPassword = string[255];
- TPasswordEvent = procedure(Sender: TObject; var Password: TPassword) of
- object;
-
- type
- { QDB-specific exceptions }
- EQDBError = class(Exception);
- EQDBListError = class(EQDBError);
- EQDBFileError = class(EQDBError);
- EQDBIndexError = class(EQDBError);
- EQDBInvalidPW = class(EQDBError);
- EQDBNoCompress = class(EQDBError);
- EQDBBadKey = class(EQDBError);
- EQDBOutOfBounds = class(EQDBIndexError);
- EQDBNoData = class(EQDBIndexError);
- EQDBReadOnly = class(EQDBIndexError);
- EQDBNoFile = class(EQDBIndexError);
-
- { TQDBList }
-
- const
- MaxBranchSize = 65532 div SizeOf(pointer);
- MaxListSize = MaxBranchSize * MaxBranchSize;
-
- type
- PLeafList = ^TLeafList;
- TLeafList = array[0..MaxBranchSize - 1] of pointer;
- PTopList = ^TTopList;
- TTopList = array[0..MaxBranchSize - 1] of PLeafList;
-
- type
- TQDBList = class(TObject)
- private
- FCapacity: longint;
- FCount: longint;
- FList: PTopList;
- LeafMask: longint; { used to find the index into a leaf }
- LeafLength: longint; { the length of the Leaf array }
- LeafSize: longint; { the memory-size of the Leaf }
- TopSize: longint; { the memory-size of the Top array }
- Power: longint; { the power of two giving the length }
- TopUsed: longint; { the number of active leaves }
- procedure AddLeaf;
- procedure SetPower(p: longint);
- protected
- function Get(Index: longint): pointer;
- procedure Grow;
- procedure Put(Index: longint; Item: pointer);
- procedure SetCapacity(NewCapacity: longint);
- procedure SetCount(NewCount: longint);
- public
- constructor Create;
- destructor Destroy; override;
- procedure Clear;
- procedure Delete(Index: longint);
- procedure Error(const ErrMsg: string; Data: longint);
- procedure Exchange(Index1, Index2: longint);
- procedure Insert(Index: longint; Item: pointer);
- property Capacity: longint read FCapacity write SetCapacity;
- property Count: longint read FCount write SetCount;
- property Items[Index: longint]: pointer read Get write Put;
- default;
- end;
-
- { TQDBStringList }
-
- TQDBStringList = class(TPersistent)
- private
- FCaseSensitive: boolean;
- FList: TQDBList;
- FSorted: boolean;
- procedure SetCaseSensitive(Value: boolean);
- procedure SetSorted(Value: boolean);
- protected
- function Get(Index: longint): string;
- function GetCapacity: longint;
- function GetCount: longint;
- function GetObject(Index: longint): TObject;
- procedure Put(Index: longint; const S: string);
- procedure PutObject(Index: longint; AObject: TObject);
- procedure SetCapacity(NewCapacity: longint);
- public
- constructor Create;
- destructor Destroy; override;
- function Add(const S: string): longint;
- function AddObject(const S: string; AObject: TObject): longint;
- procedure Clear;
- procedure Delete(Index: longint);
- procedure Error(const ErrMsg: string; Data: longint);
- procedure Exchange(Index1, Index2: longint);
- function Find(const S: string; var Index: longint): boolean;
- procedure Reverse;
- property CaseSensitive: boolean read FCaseSensitive write SetCaseSensitive;
- property Count: longint read GetCount;
- property Sorted: boolean read FSorted write SetSorted;
- property Objects[Index: longint]: TObject read GetObject write
- PutObject;
- property Strings[Index: longint]: string read Get write Put;
- default;
- end;
-
- { TIndexList }
-
- type
- TIndexList = class(TQDBStringList)
- destructor Destroy; override;
- procedure EmptyAndClear;
- end;
-
- { TCacheList }
-
- TCacheList = class(TQDBStringList)
- private
- FAttempts: longint; { number of cache hits and failures }
- FCurrentSize: longint; { ... in bytes }
- FDisposals: longint; { number of scans for LRU item }
- FMaximumSize: longint; { upper limit on size of cache }
- FOldest: longint;
- FSuccesses: longint; { number of cache hits }
- protected
- function GetFrequency: integer;
- procedure SetFrequency(Value: integer);
- procedure SetSize(Value: longint);
- public
- constructor Create;
- destructor Destroy; override;
- procedure Fetch(Stream: TStream; Place: longint);
- procedure Flush;
- procedure MakeSpace;
- procedure Remove(Key: TKey);
- procedure Statistics(var MaxSize, CurSize, CurLen, HitRatio, DropRatio:
- longint);
- procedure Store(Stream: TStream; Key: TKey);
- end;
-
- { TBFStream }
-
- type
- TBFStream = class(TFileStream)
- private
- Buffer: pchar;
- BufLen: longint;
- BufferPos: pchar;
- BytesRead: longint;
- IsDirty: boolean;
- public
- constructor Create(const FileName: string; Mode: word; BufferSize:
- longint);
- destructor Destroy; override;
- procedure AdjustBuffer;
- function GetKey(var k: TKey): boolean;
- function GetLongint(var L: longint): boolean;
- procedure PutKey(const k: TKey);
- procedure PutLongint(const L: longint);
- procedure ResetBuffer;
- function Seek(Offset: longint; Origin: word): longint; override;
- end;
-
- { TTempBFStream }
-
- type
- TTempBFStream = class(TBFStream)
- private
- FOldFileName: string;
- TmpFileName: string;
- public
- constructor Create(const OldFileName: string);
- destructor Destroy; override;
- end;
-
- { TQDB }
-
- type
- TMatchProc = function(Key: TKey; Pattern: TKey): boolean of object;
-
- type
- TQDBNavigator = class; {forward declaration }
-
- TQDB = class(TComponent)
- private
- Admin: TIndexList; { in-memory index to administrative items }
- Cache: TCacheList; { in-memory item cache }
- FAfterCancel: TNotifyEvent;
- FAfterDelete: TNotifyEvent;
- FAfterEdit: TNotifyEvent;
- FAfterInsert: TNotifyEvent;
- FAfterPost: TNotifyEvent;
- FAdminIndex: TItemIndex; { current position in admin index }
- FAggressiveUpdate: boolean; { }
- FAuthor: string40; { my name! }
- FAutoEdit: boolean;
- FBackWild: char; { wildcard stands for any chars at back of key }
- FBeforeCancel: TNotifyEvent;
- FBeforeDelete: TNotifyEvent;
- FBeforeEdit: TNotifyEvent;
- FBeforeInsert: TNotifyEvent;
- FBeforePost: TNotifyEvent;
- FBoF: boolean; { at beginning of file ? }
- FCompression: boolean; { compress changes ? }
- FCount: TItemIndex; { number of items in Index }
- FEditing: boolean;
- FEoF: boolean; { at end of file ? }
- FFileAge: longint; { age of the QDB file when opened }
- FFileName: string; { name of QDB file }
- FFilter: TKey;
- FForceOverwrite: boolean; { restricts access to certain keys }
- FFrontWild: char; { wildcard stands for any chars at front of key }
- Filtered: boolean; { is FFilter = '' ? }
- FGrepMatch: boolean; { use grep-style match }
- FInserting: boolean;
- FItemIndex: TItemIndex; { current position in Index }
- FKey: TKey; { key of current item }
- Matches: TMatchProc; { the matching procedure to use }
- FMatchWholeWord: boolean; { in patetrn matching and filtering }
- FOnAdded: TNotifyEvent;
- FOnChanged: TNotifyEvent;
- FOnDeleted: TNotifyEvent;
- FOnDemandPassWord: TPasswordEvent;
- FOnFileAssigned: TNotifyEvent;
- FOnFound: TNotifyEvent;
- FOnKilled: TNotifyEvent;
- FOnNavigate: TNotifyEvent;
- FPassWord: TPassword; { up to 255 chars }
- FProgressUpdate: TProgressEvent;
- FQDBNavigator: TQDBNavigator;
- FReadOnly: boolean; { governs file access }
- FReady: boolean; { true iff a file is open and ready for access }
- FSaveOnClose: boolean; { if true closing the file saves it , def true }
- FExpandedFileNames: boolean; { if true FileName is made absolute , def true }
- FUpdating: longint; { count Begin/End Update calls }
- FVersion: string05; { QDB version e.g. '1.00' }
- index: TIndexList; { in-memory index to file }
- IsDirty: boolean; { has the file been changed ? }
- MonitorKind: TProgressOrigin;
- MonitorInterval: longint; { how often to update progress monitor }
- QIXFile: TBFStream; { working index-file stream }
- QIXFileName: string; { name of the working index-file }
- FBeforeKill: TConfirmEvent;
- FBeforeOverWrite: TConfirmEvent;
- FWarnNoData: TWarningEvent;
- FWarnOutOfBounds: TWarningEvent;
- FWarnReadOnly: TWarningEvent;
- procedure AdminAddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey);
- procedure AdminChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
- function AdminExactMatch(Key: TKey): boolean;
- function AdminGetBoolean(Key: TKey): boolean;
- function AdminGetInteger(Key: TKey): longint;
- procedure AdminGetItem(ItemPtr: pointer);
- function AdminGetString(Key: TKey): string;
- function AdminItemSize: TDataIndex;
- procedure AdminSetBoolean(Key: TKey; b: boolean);
- procedure AdminSetInteger(Key: TKey; n: longint);
- procedure AdminSetString(Key: TKey; const S: string);
- procedure CloseQDB;
- procedure CreateQDB;
- procedure FileError(ErrCode: integer; SDefault: string);
- function GetCacheFrequency: integer;
- function GetCacheSize: longint;
- function GetFileName: TQDBFileName;
- function GetFilteredCount: TItemIndex;
- function GetItemSize(Value: TItemIndex): TDataIndex;
- function GetKey(Value: TItemIndex): TKey;
- function GetKeyCase: boolean;
- function GetStr(n: TItemIndex): string;
- function GetStrByKey(Key: TKey): string;
- function GetThisItemSize: TDataIndex;
- function GetThisStr: string;
- procedure IndexError(ErrMsg: string);
- function ItemIsCompressed(Value: TItemIndex): boolean;
- procedure LoadIndex;
- procedure MonitorSetup(const Max: longint; const Kind: TProgressOrigin);
- procedure MonitorUpdate(const n: longint);
- procedure MonitorZero;
- procedure OpenQDB;
- procedure SaveIndex;
- procedure SetCacheFrequency(Value: integer);
- procedure SetCacheSize(Value: longint);
- procedure SetDummyAuthor(Value: string40);
- procedure SetDummyVersion(Value: string05);
- procedure SetFilter(Value: TKey);
- procedure SetItemIndex(Value: TItemIndex);
- procedure SetKeyCase(Value: boolean);
- procedure SetReadOnly(Value: boolean);
- procedure SetReady(Value: boolean);
- procedure SetStr(n: TItemIndex; const Value: string);
- procedure SetStrByKey(Key: TKey; const Value: string);
- procedure SetThisStr(const Value: string);
- procedure Splice;
- procedure Split;
- protected
- QDBFile: TFileStream; { working item-file stream }
- QDBFileName: string; { name of the working data-file }
- Restructuring: boolean;
- procedure AboutToKill(var OK: boolean);
- procedure AboutToOverWrite(var OK: boolean);
- procedure Added;
- procedure CannotChange;
- procedure Changed;
- procedure Deleted;
- procedure DemandPassword;
- procedure DoCancel; virtual;
- procedure DoDelete; virtual;
- procedure DoEdit; virtual;
- procedure DoInsert; virtual;
- procedure DoPost; virtual;
- procedure FileAssigned;
- function FileToRecover: string;
- procedure ForceOverwrite(Value: boolean);
- procedure Found;
- function GrepMatches(Key: TKey; Pattern: TKey): boolean;
- procedure Killed;
- function Live: boolean;
- procedure Navigate;
- procedure NoData;
- procedure Notification(AComponent: TComponent; Operation: TOperation); override;
- procedure OutOfBounds;
- procedure SetFileName(Value: TQDBFileName); virtual;
- procedure SetGrepMatch(Value: boolean);
- procedure SetLinkToNavigator(Value: TQDBNavigator);
- procedure SignalProgress(Percent: TPercentage; Kind: TProgressOrigin);
- function SimpleMatches(Key: TKey; Pattern: TKey): boolean;
- property AutoEdit: boolean read FAutoEdit write FAutoEdit;
- property Editing: boolean read FEditing;
- property Inserting: boolean read Finserting;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Add(Stream: TStream; Key: TKey);
- procedure AddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey);
- procedure AddStreamItem(Stream: TStream; Key: TKey);
- procedure AdminClear(StartOfKey: TKey);
- function AdminCount: TItemIndex;
- procedure AdminDelete(Key: TKey);
- function AdminKeyExists(Key: TKey): boolean;
- function AdminKeys(Keys: TStrings; StartOfKey: TKey): longint;
- procedure AssignKeyList(Keys: TStrings);
- procedure BeginUpdate;
- procedure Cancel;
- procedure CacheFlush;
- procedure CacheStatistics(var MaxSize, CurSize, CurLen, HitRatio, DropRatio: longint);
- procedure Change(Stream: TStream);
- procedure ChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
- {// Alex procedure ChangeKey(key: TKey);}
- procedure ChangeStreamItem(Stream: TStream);
- function CloseMatch(Partialkey: TKey): boolean;
- procedure Compress;
- procedure Delete;
- procedure DeleteItem;
- procedure Edit;
- procedure EndUpdate;
- function ExactMatch(Key: TKey): boolean;
- procedure Expand;
- procedure FirstItem; virtual;
- procedure Get(Stream: TStream);
- procedure GetItem(ItemPtr: pointer);
- procedure GetStreamItem(Stream: TStream);
- procedure Insert;
- function KeyExists(Key: TKey): boolean;
- procedure Kill;
- procedure LastItem; virtual;
- procedure NextItem; virtual;
- function OrphanToRecover: boolean;
- procedure Pack;
- function PartialMatch(StartOfKey: TKey): boolean;
- procedure PartialMatchInit;
- function PatternMatch(Pattern: TKey): boolean;
- procedure PatternMatchInit;
- procedure Post;
- procedure PrepareToAdd(numberofitems: TItemIndex);
- procedure PrevItem; virtual;
- procedure Recover(NewFileName: string);
- procedure Refresh; virtual;
- procedure Save;
- procedure SaveAs(NewName: string);
- procedure SetMatchChars(Front: char; back: char);
- procedure UpdateNavigator;
- property AdminAsBoolean[Key: TKey]: boolean read AdminGetBoolean write AdminSetBoolean;
- property AdminAsInteger[Key: TKey]: longint read AdminGetInteger write AdminSetInteger;
- property AdminAsString[Key: TKey]: string read AdminGetString write AdminSetString;
- property BoF: boolean read FBoF;
- property Count: TItemIndex read FCount;
- property EoF: boolean read FEoF;
- property FilteredCount: TItemIndex read GetFilteredCount;
- property Key: TKey read FKey;
- property KeyCaseSensitive: boolean read GetKeyCase write SetKeyCase;
- property CurrentItem: string read GetThisStr write SetThisStr;
- property ItemIndex: TItemIndex read FItemIndex write SetItemIndex;
- property Items[n: TItemIndex]: string read GetStr write SetStr;
- property ItemsByKey[Key: TKey]: string read GetStrByKey write SetStrByKey;
- default;
- property ItemSize: TDataIndex read GetThisItemSize;
- property MatchWholeWord: boolean read FMatchWholeWord write FMatchWholeWord;
- property Password: TPassword read FPassWord write FPassWord stored false;
- property Ready: boolean read FReady;
- published
- property AboutAuthor: string40 read FAuthor write SetDummyAuthor;
- property AboutVersion: string05 read FVersion write SetDummyVersion;
- property AfterCancel: TNotifyEvent read FAfterCancel write FAfterCancel;
- property AfterDelete: TNotifyEvent read FAfterDelete write FAfterDelete;
- property AfterEdit: TNotifyEvent read FAfterEdit write FAfterEdit;
- property AfterInsert: TNotifyEvent read FAfterInsert write FAfterInsert;
- property AfterPost: TNotifyEvent read FAfterPost write FAfterPost;
- property AggressiveUpdate: boolean read FAggressiveUpdate write FAggressiveUpdate;
- property BeforeCancel: TNotifyEvent read FBeforeCancel write FBeforeCancel;
- property BeforeDelete: TNotifyEvent read FBeforeDelete write FBeforeDelete;
- property BeforeEdit: TNotifyEvent read FBeforeEdit write FBeforeEdit;
- property BeforeInsert: TNotifyEvent read FBeforeInsert write FBeforeInsert;
- property BeforeKill: TConfirmEvent read FBeforeKill write FBeforeKill;
- property BeforePost: TNotifyEvent read FBeforePost write FBeforePost;
- property BeforeOverWrite: TConfirmEvent read FBeforeOverWrite write FBeforeOverWrite;
- property CacheFrequency: integer read GetCacheFrequency write SetCacheFrequency;
- property CacheSize: longint read GetCacheSize write SetCacheSize;
- property Compression: boolean read FCompression write FCompression;
- property FileName: TQDBFileName read GetFileName write SetFileName;
- property Filter: TKey read FFilter write SetFilter;
- property ProgressUpdate: TProgressEvent read FProgressUpdate write FProgressUpdate;
- property ReadOnly: boolean read FReadOnly write SetReadOnly;
- property SaveOnClose: boolean read FSaveOnClose write FSaveOnClose;
- property ExpandedFileNames: boolean read FExpandedFileNames write FExpandedFileNames;
- property UseGrepMatch: boolean read FGrepMatch write SetGrepMatch;
- property WarnNoData: TWarningEvent read FWarnNoData write FWarnNoData;
- property WarnOutOfBounds: TWarningEvent read FWarnOutOfBounds write FWarnOutOfBounds;
- property WarnReadOnly: TWarningEvent read FWarnReadOnly write FWarnReadOnly;
- property OnAdded: TNotifyEvent read FOnAdded write FOnAdded;
- property OnChanged: TNotifyEvent read FOnChanged write FOnChanged;
- property OnDeleted: TNotifyEvent read FOnDeleted write FOnDeleted;
- property OnDemandPassword: TPasswordEvent read FOnDemandPassWord write FOnDemandPassWord;
- property OnFileAssigned: TNotifyEvent read FOnFileAssigned write FOnFileAssigned;
- property OnFound: TNotifyEvent read FOnFound write FOnFound;
- property OnKilled: TNotifyEvent read FOnKilled write FOnKilled;
- property OnNavigate: TNotifyEvent read FOnNavigate write FOnNavigate;
- end;
-
- { TQDBNavigator }
-
- { This is a modified version of DBNavigator. Code from the VCL library }
- { is copyright Borland. }
- { Copyright (c) 1995-1997 Borland International. All Rights Reserved. }
-
- {type}
- TNavButton = class;
-
- TNavGlyph = (ngEnabled, ngDisabled);
- TNavOrientation = (noAuto, noHoriz, noVert);
- TNavigateBtn = (nbFirst, nbPrev, nbNext, nbLast,
- nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh);
- TButtonSet = set of TNavigateBtn;
- TNavButtonStyle = set of (nsAllowTimer, nsFocusRect);
-
- TNavClickEvent = procedure(Sender: TObject; Button: TNavigateBtn) of object;
- TBtnPressEvent = procedure(Sender: TObject; Q: TQDB) of object;
-
- TQDBNavigator = class(TCustomPanel)
- private
- ButtonHeight: integer;
- ButtonWidth: integer;
- FBeforeAction: TNavClickEvent;
- FFlat: boolean;
- FHints: TStrings;
- FocusedButton: TNavigateBtn;
- FOnCancel: TBtnPressEvent;
- FOnDelete: TBtnPressEvent;
- FOnEdit: TBtnPressEvent;
- FOnFirst: TBtnPressEvent;
- FOnInsert: TBtnPressEvent;
- FOnLast: TBtnPressEvent;
- FOnNavClick: TNavClickEvent;
- FOnNext: TBtnPressEvent;
- FOnPost: TBtnPressEvent;
- FOnPrev: TBtnPressEvent;
- FOnRefresh: TBtnPressEvent;
- FOrientation: TNavOrientation;
- FQDB: TQDB;
- FVisibleButtons: TButtonSet;
- MinBtnSize: TPoint;
- procedure _Click(Sender: TObject);
- procedure AdjustSize(var W: integer; var H: integer);
- procedure BtnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: integer);
- function GetEnabled: boolean;
- function GetGlyph(Btn: TNavigateBtn): Graphics.TBitmap;
- procedure HintsChanged(Sender: TObject);
- procedure InitButtons;
- procedure InitHints;
- procedure SetEnabled(Value: boolean);
- procedure SetFlat(Value: boolean);
- procedure SetGlyph(Btn: TNavigateBtn; Value: Graphics.TBitmap);
- procedure SetHints(Value: TStrings);
- procedure SetOrientation(Value: TNavOrientation);
- procedure SetVisible(Value: TButtonSet);
- procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
- procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
- procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
- procedure WMSize(var Message: TWMSize); message WM_SIZE;
- protected
- Buttons: array[TNavigateBtn] of TNavButton;
- procedure Cancel;
- procedure Delete;
- procedure Edit;
- procedure First;
- procedure Insert;
- procedure KeyDown(var Key: word; Shift: TShiftState); override;
- procedure Last;
- procedure Loaded; override;
- procedure Next;
- procedure Notification(AComponent: TComponent; Operation: TOperation); override;
- procedure Post;
- procedure Prev;
- procedure QDBStateChanged;
- procedure Refresh;
- procedure SetQDB(Value: TQDB); virtual;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure BtnClick(Index: TNavigateBtn); virtual;
- procedure SetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
- property Glyphs[Btn: TNavigateBtn]: Graphics.TBitmap read GetGlyph write SetGlyph;
- published
- property Align;
- property BeforeAction: TNavClickEvent read FBeforeAction write FBeforeAction;
- property Ctl3D;
- property DragCursor;
- property DragMode;
- property Enabled read GetEnabled write SetEnabled;
- property Flat: boolean read FFlat write SetFlat default false;
- property Hints: TStrings read FHints write SetHints;
- property Orientation: TNavOrientation read FOrientation write SetOrientation default noAuto;
- property ParentCtl3D;
- property ParentShowHint;
- property QDB: TQDB read FQDB write SetQDB;
- property ShowHint;
- property TabOrder;
- property TabStop;
- property Visible;
- property VisibleButtons: TButtonSet read FVisibleButtons write SetVisible
- default [nbFirst, nbPrev, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost, nbCancel, nbRefresh];
- property OnCancel: TBtnPressEvent read FOnCancel write FOnCancel;
- property OnClick: TNavClickEvent read FOnNavClick write FOnNavClick;
- property OnDblClick;
- property OnDelete: TBtnPressEvent read FOnDelete write FOnDelete;
- property OnDragDrop;
- property OnDragOver;
- property OnEdit: TBtnPressEvent read FOnEdit write FOnEdit;
- property OnEndDrag;
- property OnEnter;
- property OnExit;
- property OnFirst: TBtnPressEvent read FOnFirst write FOnFirst;
- property OnInsert: TBtnPressEvent read FOnInsert write FOnInsert;
- property OnLast: TBtnPressEvent read FOnLast write FOnLast;
- property OnNext: TBtnPressEvent read FOnNext write FOnNext;
- property OnPost: TBtnPressEvent read FOnPost write FOnPost;
- property OnPrev: TBtnPressEvent read FOnPrev write FOnPrev;
- property OnRefresh: TBtnPressEvent read FOnRefresh write FOnRefresh;
- property OnResize;
- end;
-
- {type}
- TNavButton = class(TSpeedButton)
- private
- FIndex: TNavigateBtn;
- FNavStyle: TNavButtonStyle;
- FRepeatTimer: TTimer;
- procedure TimerExpired(Sender: TObject);
- protected
- procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
- procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
- procedure Paint; override;
- public
- destructor Destroy; override;
- property Index: TNavigateBtn read FIndex write FIndex;
- property NavStyle: TNavButtonStyle read FNavStyle write FNavStyle;
- end;
-
- function TempFileName(Prefix: string): string;
- procedure RenameOrMoveFile(const SrcFileName, DstFileName: string);
-
- implementation
-
- uses
- qdbu;
-
- {$IFDEF WIN32}
- {$R QDB.R32}
- {$ELSE}
- {$R QDB.R16}
- {$ENDIF}
-
- { the codes for the messages in QDB.R16 or QDB.R32}
- const
- SMissing = 'Could not find the file %s';
- SCorrupt = 'The file %s is not a valid QDB file';
- SDoorOpen = 'The drive you are trying to access is not ready';
- SReadOnly = '%s is marked as read-only';
- STooMany = 'No more file handles are available';
- SShareError = 'The file %s seems to be in use by another program';
- SDiskFull = 'The drive is full';
- SUnknownError = 'Unidentified problem - %s';
- SIndexAdd = 'Not enough memory to extend the index to %s';
- SCannotCopy = 'Unable to copy %s';
- SDataAdd = 'Unable to extend the file %s';
- SDuplicateKey = 'Duplicate keys are not allowed';
- SSortedListError = 'Cannot insert into a sorted list';
- SOutOfBounds = 'The list index is out of bounds';
- STempFile = 'Could not create a necessary temporary file';
- SNoFile = 'Illegal operation - no file assigned';
- SNoMemory = 'Insufficient memory to compress or expand';
- SBadKey = 'The key ''%s'' does not exist';
- SBadPassword = 'The password you have provided is invalid';
- SNoData = 'File %s is empty';
-
- { Flags in the TIndex.Ext field }
-
- type
- TFlags = 0..31;
- TFlagSet = set of TFlags;
-
- const
- IsAdminItem: TFlags = 0; {Admin items}
- IsCompressed: TFlags = 1; {Compressed items}
-
- { ******* Utility routines ******* }
-
- { Allocates memory for a buffer -- first tries to get the }
- { RequestedSize but if not available keeps halving the size }
- { until a block can be allocated. The actual amount allocated }
- { is returned as Result. }
-
- function GetBuffer(var Buffer: pointer; RequestedSize: longint): longint;
- var
- AllocatedOK: boolean;
- AllocatedSize: longint;
- begin
- AllocatedSize := 0;
- { make sure request is in range }
- if RequestedSize < 1024 then
- RequestedSize := 1024;
- {$IFNDEF WIN32}
- if RequestedSize > (1024 * 63) then
- RequestedSize := (1024 * 63);
- {$ELSE}
- if RequestedSize > (1024 * 512) then
- RequestedSize := (1024 * 512);
- {$ENDIF}
- AllocatedOK := false;
- while not AllocatedOK do
- begin
- try
- GetMem(Buffer, RequestedSize);
- AllocatedSize := RequestedSize;
- AllocatedOK := true;
- except
- { keep halving the request until successful }
- on EOutOfMemory do
- RequestedSize := RequestedSize div 2;
- end;
- end;
- Result := AllocatedSize;
- end;
-
- {*******************************************************************
- *
- * Stream Compression
- *
- * based on the LZRW1/KH compression algorithm posted by Kurt Haenen
- * to SWAG as 'lzrw1' and modified for Delphi by D. Heijl
- * (Danny.Heijl@cevi.be)
- *
- * Haenen states, 'The algoritm is not as good as LZH, but can compete
- * with Lempel-Ziff. It's the fastest one I've encountered up to now.'
- *
- * The procedures below are the ones actually used in QDB
- *
- * function squashstream(src, dst: tstream): longint;
- * compresses the whole of the src stream to the current place in
- * dst and returns the number of bytes written to dst
- *
- * procedure unsquashstream(src, dst: tstream; bytes: longint);
- * expands the requested number of bytes from the current place in
- * the src stream to dst (which should be empty)
- *
- * The procedures GetMatch, Squash, and Unsquash do the actual work.
- *
- ******************************************************************************}
-
- { we want to turn off range checking temporarily }
- {$IFOPT R+}
- {$DEFINE RON}
- {$R-}
- {$ENDIF}
-
- {$IFDEF WIN32}
- type
- int16 = smallint;
- {$ELSE}
- type
- int16 = integer;
- {$ENDIF}
-
- const
- BufferMaxSize = 32768;
- BufferMax = BufferMaxSize - 1;
- flag_copied = $80;
- flag_compress = $40;
-
- type
- BufferIndex = 0..BufferMax + 15;
- BufferSize = 0..BufferMaxSize;
- BufferArray = array[BufferIndex] of Byte;
- Bufferptr = ^BufferArray;
- HashTable = array[0..4095] of int16;
- HashTabPtr = ^HashTable;
-
- { turn off overflow testing temporarily }
- {$IFOPT Q+}
- {$DEFINE QON}
- {$Q-}
- {$ENDIF}
-
- {check if this string has already been seen in the current 4 KB window }
-
- function GetMatch(Source: Bufferptr; X: BufferIndex; SourceSize: BufferSize;
- Hash: HashTabPtr; var size: word; var Pos: BufferIndex): boolean;
- var
- HashValue: word;
- TmpHash: int16;
- begin
- HashValue := (40543 * ((((Source^[X] shl 4) xor Source^[X + 1]) shl
- 4) xor
- Source^[X + 2]) shr 4) and $0FFF;
- Result := false;
- TmpHash := Hash^[HashValue];
- if (TmpHash <> -1) and (X - TmpHash < 4096) then
- begin
- Pos := TmpHash;
- size := 0;
- while ((size < 18) and (Source^[X + size] = Source^[Pos + size])
- and (X + size < SourceSize)) do
- begin
- inc(size);
- end;
- Result := (size >= 3)
- end;
- Hash^[HashValue] := X;
- end;
- {$IFDEF QON}
- {$UNDEF QON}
- {$Q+}
- {$ENDIF}
-
- { compress a buffer of max. 32 KB }
-
- function Squash(Source, Dest: Bufferptr; SourceSize: BufferSize; Hash:
- HashTabPtr): BufferSize;
- var
- bit, command, size: word;
- Key: word;
- X, Y, Z, Pos: BufferIndex;
- begin
- FillChar(Hash^, SizeOf(HashTable), $FF);
- Dest^[0] := flag_compress;
- X := 0;
- Y := 3;
- Z := 1;
- bit := 0;
- command := 0;
- while (X < SourceSize) and (Y <= SourceSize) do
- begin
- if (bit > 15) then
- begin
- Dest^[Z] := Hi(command);
- Dest^[Z + 1] := Lo(command);
- Z := Y;
- bit := 0;
- inc(Y, 2)
- end;
- size := 1;
- while ((Source^[X] = Source^[X + size]) and (size < $FFF)
- and (X + size < SourceSize)) do
- begin
- inc(size);
- end;
- if (size >= 16) then
- begin
- Dest^[Y] := 0;
- Dest^[Y + 1] := Hi(size - 16);
- Dest^[Y + 2] := Lo(size - 16);
- Dest^[Y + 3] := Source^[X];
- inc(Y, 4);
- inc(X, size);
- command := (command shl 1) + 1;
- end
- else
- begin { not size >= 16 }
- if (GetMatch(Source, X, SourceSize, Hash, size, Pos)) then
- begin
- Key := ((X - Pos) shl 4) + (size - 3);
- Dest^[Y] := Hi(Key);
- Dest^[Y + 1] := Lo(Key);
- inc(Y, 2);
- inc(X, size);
- command := (command shl 1) + 1
- end
- else
- begin
- Dest^[Y] := Source^[X];
- inc(Y);
- inc(X);
- command := command shl 1
- end;
- end; { size <= 16 }
- inc(bit);
- end; { while x < sourcesize ... }
- command := command shl (16 - bit);
- Dest^[Z] := Hi(command);
- Dest^[Z + 1] := Lo(command);
- if (Y > SourceSize) then
- begin
- Move(Source^[0], Dest^[1], SourceSize);
- Dest^[0] := flag_copied;
- Y := succ(SourceSize)
- end;
- Result := Y
- end;
-
- { decompress a buffer of max 32 KB }
-
- function Unsquash(Source, Dest: Bufferptr; SourceSize: BufferSize):
- BufferSize;
- var
- X, Y, Pos: BufferIndex;
- command, size, k: word;
- bit: Byte;
- Savey: BufferIndex; { unsafe for-loop variable Y -- dh --}
- begin
- if (SourceSize <= 1) then
- begin { correction of a bug found by Dominique Willems <Domus@compuserve.com>}
- Result := 0;
- exit;
- end;
- if (Source^[0] = flag_copied) then
- begin
- for Y := 1 to pred(SourceSize) do
- begin
- Dest^[pred(Y)] := Source^[Y];
- Savey := Y;
- end;
- Y := Savey;
- end
- else
- begin
- Y := 0;
- X := 3;
- command := (Source^[1] shl 8) + Source^[2];
- bit := 16;
- while (X < SourceSize) do
- begin
- if (bit = 0) then
- begin
- command := (Source^[X] shl 8) + Source^[X + 1];
- bit := 16;
- inc(X, 2)
- end;
- if ((command and $8000) = 0) then
- begin
- Dest^[Y] := Source^[X];
- inc(X);
- inc(Y)
- end
- else
- begin { command and $8000 }
- Pos := ((Source^[X] shl 4) + (Source^[X + 1] shr 4));
- if (Pos = 0) then
- begin
- size := (Source^[X + 1] shl 8) + Source^[X + 2] + 15;
- for k := 0 to size do
- begin
- Dest^[Y + k] := Source^[X + 3];
- end;
- inc(X, 4);
- inc(Y, size + 1)
- end
- else
- begin { pos = 0 }
- size := (Source^[X + 1] and $0F) + 2;
- for k := 0 to size do
- Dest^[Y + k] := Dest^[Y - Pos + k];
- inc(X, 2);
- inc(Y, size + 1)
- end; { pos = 0 }
- end; { command and $8000 }
- command := command shl 1;
- dec(bit);
- end; { while x < sourcesize }
- end;
- Result := Y;
- end; { Unsquash }
-
- function SquashStream(Src, Dst: TStream): longint;
- var
- inp,
- outp: Bufferptr;
- ins,
- outs: word;
- Hash: HashTabPtr;
- begin
- Result := 0;
- Src.Seek(0, 0);
- try
- GetMem(inp, BufferMaxSize);
- except
- raise EQDBNoCompress.Create(SNoMemory);
- end;
- try
- try
- GetMem(outp, BufferMaxSize);
- except
- raise EQDBNoCompress.Create(SNoMemory);
- end;
- try
- try
- GetMem(Hash, SizeOf(HashTable));
- except
- raise EQDBNoCompress.Create(SNoMemory);
- end;
- try
- while Src.Position < Src.size do
- begin
- ins := Src.Read(inp^, BufferMaxSize);
- outs := Squash(inp, outp, ins, Hash);
- inc(Result, Dst.Write(outs, SizeOf(outs)));
- inc(Result, Dst.Write(outp^, outs));
- end;
- finally
- FreeMem(Hash, SizeOf(HashTable));
- end;
- finally
- FreeMem(outp, BufferMaxSize);
- end;
- finally
- FreeMem(inp, BufferMaxSize);
- end;
- end;
-
- procedure UnSquashStream(Src, Dst: TStream; Bytes: longint);
- var
- inp,
- outp: Bufferptr;
- ins,
- outs: word;
- Tot: longint;
- begin
- Dst.Seek(0, 0);
- Tot := 0;
- try
- GetMem(inp, BufferMaxSize);
- except
- raise EQDBNoCompress.Create(SNoMemory);
- end;
- try
- try
- GetMem(outp, BufferMaxSize);
- except
- raise EQDBNoCompress.Create(SNoMemory);
- end;
- try
- while Tot < Bytes do
- begin
- inc(Tot, Src.Read(ins, SizeOf(ins)));
- ins := Src.Read(inp^, ins);
- inc(Tot, ins);
- outs := Unsquash(inp, outp, ins);
- Dst.Write(outp^, outs);
- end;
- finally
- FreeMem(outp, BufferMaxSize);
- end;
- finally
- FreeMem(inp, BufferMaxSize);
- end;
- Dst.Seek(0, 0);
- end;
-
- { restore the previous range checking state }
- {$IFDEF RON}
- {$UNDEF RON}
- {$R+}
- {$ENDIF}
-
- { TQDBList }
-
- const
- PowerMin = 1; { governs the minimum capacity of the list }
- { i.e.. 2^(2*PowerMin) = 4 }
-
- constructor TQDBList.Create;
- begin
- inherited Create;
- FCount := 0;
- FList := nil;
- TopUsed := 0;
- SetPower(PowerMin);
- FCapacity := 0;
- SetCapacity(0);
- end;
-
- destructor TQDBList.Destroy;
- begin
- while TopUsed > 0 do
- begin
- FreeMem(FList^[TopUsed - 1], LeafSize);
- dec(TopUsed);
- end;
- if FList <> nil then
- begin
- FreeMem(FList, TopSize);
- FList := nil;
- end;
- inherited Destroy;
- end;
-
- procedure TQDBList.AddLeaf;
- var
- NewLeaf: PLeafList;
- begin
- try
- GetMem(NewLeaf, LeafSize);
- FList^[TopUsed] := NewLeaf;
- inc(TopUsed);
- except
- on EOutOfMemory do
- Error(SNoMemory, 0)
- else
- raise;
- end;
- end;
-
- procedure TQDBList.Clear;
- begin
- while TopUsed > 0 do
- begin
- FreeMem(FList^[TopUsed - 1], LeafSize);
- dec(TopUsed);
- end;
- FCount := 0;
- SetCapacity(0);
- end;
-
- procedure TQDBList.Delete(Index: longint);
- { messy ... we have to move items from one leaf to the next }
- var
- i: longint;
- amount: longint;
- begin
- { how many elements do we have to shift in the first leaf }
- amount := LeafLength - 1 - (Index and LeafMask);
- { move the first chunk left }
- if amount > 0 then
- System.Move(FList^[(Index shr Power)]^[(Index + 1) and LeafMask],
- FList^[(Index shr Power)]^[Index and LeafMask], amount * SizeOf(
- pointer));
- { then for each leaf on up }
- for i := (Index shr Power) to TopUsed - 2 do
- begin
- { bring one item down from the end to the front }
- FList^[i]^[LeafLength - 1] := FList^[i + 1]^[0];
- { shift the rest left one place }
- System.Move(FList^[i + 1]^[1], FList^[i + 1]^[0], LeafSize -
- SizeOf(pointer));
- end;
- dec(FCount);
- { if we've emptied a leaf we can free the space }
- if (FCount = 0) or (((FCount - 1) shr Power) < (TopUsed - 1)) then
- begin
- FreeMem(FList^[TopUsed - 1], LeafSize);
- dec(TopUsed);
- end;
- end;
-
- procedure TQDBList.Error(const ErrMsg: string; Data: longint);
- var
- StackTop: record
- end;
- Stack: record
- BPorEBP: integer; { 16 bit: BP, 32 bit: EBP }
- ReturnAddress: pointer;
- end absolute StackTop;
- begin
- raise EQDBListError.CreateFmt(ErrMsg, [Data])at Stack.ReturnAddress;
- end;
-
- procedure TQDBList.Exchange(Index1, Index2: longint);
- var
- Item: pointer;
- begin
- Item := FList^[(Index1 shr Power)]^[(Index1 and LeafMask)];
- FList^[(Index1 shr Power)]^[(Index1 and LeafMask)] := FList^[(Index2
- shr Power)]^[(Index2 and LeafMask)];
- FList^[(Index2 shr Power)]^[(Index2 and LeafMask)] := Item;
- end;
-
- function TQDBList.Get(Index: longint): pointer;
- begin
- Result := FList^[(Index shr Power)]^[(Index and LeafMask)];
- end;
-
- procedure TQDBList.Grow;
- begin
- { SetCapacity will choose a suitable new value -- the list }
- { capacity grows by powers of two }
- SetCapacity(FCapacity + 1);
- end;
-
- procedure TQDBList.Insert(Index: longint; Item: pointer);
- { messy ... we have to move elements from leaf to leaf }
- var
- i: longint;
- amount: longint;
- begin
- { make room if necessary }
- if FCount = FCapacity then
- Grow;
- { add another leaf if needed }
- if (FCount and LeafMask) = 0 then
- AddLeaf;
- { for each leaf from the top down to the place of insertion }
- for i := TopUsed - 1 downto ((Index shr Power) + 1) do
- begin
- { shift everything one place right }
- System.Move(FList^[i]^[0], FList^[i]^[1], LeafSize - SizeOf(pointer
- ));
- { bring one item up from the end of the previous leaf }
- FList^[i]^[0] := FList^[i - 1]^[LeafLength - 1];
- end;
- { how many elements to shift along }
- amount := LeafLength - 1 - (Index and LeafMask);
- { shift right to make room for new item }
- System.Move(FList^[Index shr Power]^[(Index and LeafMask)],
- FList^[Index shr Power]^[(Index and LeafMask) + 1], amount * SizeOf(
- pointer));
- { insert the item itself }
- FList^[(Index shr Power)]^[(Index and LeafMask)] := Item;
- inc(FCount);
- end;
-
- procedure TQDBList.Put(Index: longint; Item: pointer);
- begin
- FList^[(Index shr Power)]^[(Index and LeafMask)] := Item;
- end;
-
- procedure TQDBList.SetCapacity(NewCapacity: longint);
- { a lot of business goes on in here ... }
- var
- NewPower: longint;
- NewSize: longint;
- NewList: PTopList;
- NewLeaf: PLeafList;
- NewTopUsed: longint;
- Ratio: longint;
- i, j: longint;
-
- function RecommendedPower(NewCapacity: longint): longint;
- begin
- { compute the root of s to the nearest greater power of 2 }
- Result := PowerMin;
- while NewCapacity >= (1 shl (Result shl 1)) do
- inc(Result);
- end;
-
- begin
- { calculate the parameters of the 'new' qlist }
- NewPower := RecommendedPower(NewCapacity);
- NewSize := (1 shl NewPower) * SizeOf(pointer);
- NewCapacity := (1 shl (NewPower shl 1));
- if (NewCapacity < FCount) or (NewCapacity > MaxListSize) then
- Error(SOutOfBounds, NewCapacity);
- if NewCapacity <> FCapacity then
- begin
- { begin to build a new qlist }
- try
- GetMem(NewList, NewSize);
- except
- on EOutOfMemory do
- Error(SNoMemory, 0)
- else
- raise
- end;
- if FCount > 0 then
- begin
- { only relevant if the list is not empty }
- NewTopUsed := ((FCount - 1) shr NewPower) + 1;
- { how many old leaves fit into a new one }
- Ratio := (NewSize div LeafSize);
- { for each old leaf }
- for i := 0 to TopUsed - 1 do
- begin
- { if a new leaf is needed }
- if i mod Ratio = 0 then
- begin
- try
- { add a new leaf }
- GetMem(NewLeaf, NewSize);
- except
- on EOutOfMemory do
- { get rid of the partly built qlist }
- begin
- j := i;
- dec(j, Ratio);
- while j >= 0 do
- FreeMem(NewList^[j], NewSize);
- FreeMem(NewList, NewSize);
- Error(SNoMemory, 0);
- end
- else
- raise;
- end;
- { put the leaf into the tree }
- NewList^[i div Ratio] := NewLeaf;
- end;
- { move the old leaf to its place in the new }
- System.Move(FList^[i]^[0], NewList^[i div Ratio]^[(LeafLength *
- (i mod Ratio))], LeafSize);
- { get rid of the old leaf }
- FreeMem(FList^[i], LeafSize);
- end;
- TopUsed := NewTopUsed;
- end;
- { get rid of the now empty old qlist }
- if FList <> nil then
- FreeMem(FList, TopSize);
- { assign the new qlist instead }
- FList := NewList;
- { adjust the qlist parameters }
- SetPower(NewPower);
- FCapacity := NewCapacity;
- end;
- end;
-
- procedure TQDBList.SetCount(NewCount: longint);
- var
- i: longint;
- begin
- if (NewCount < 0) or (NewCount > MaxListSize) then
- Error(SOutOfBounds, NewCount);
- if NewCount > FCapacity then
- SetCapacity(NewCount);
- { if we are shrinking the list we blank out the unwanted }
- { items -- if they point to anything there'll be a leak }
- if NewCount > FCount then
- for i := FCount to NewCount do
- FList^[(i shr Power)]^[(i and LeafMask)] := nil;
- FCount := NewCount;
- end;
-
- procedure TQDBList.SetPower(p: longint);
- begin
- Power := p;
- LeafLength := (1 shl Power);
- LeafSize := LeafLength * SizeOf(pointer);
- LeafMask := LeafLength - 1;
- TopSize := LeafSize;
- end;
-
- { TQDBStringList }
-
- type
- PStrItem = ^TStrItem;
- TStrItem = record
- FString: pchar;
- FObject: TObject;
- end;
-
- constructor TQDBStringList.Create;
- begin
- inherited Create;
- FList := TQDBList.Create;
- end;
-
- destructor TQDBStringList.Destroy;
- begin
- Clear;
- FList.Free;
- inherited Destroy;
- end;
-
- procedure DisposeStrItem(p: PStrItem);
- begin
- FreeMem(p^.FString, StrLen(p^.FString) + 1);
- FreeMem(p, SizeOf(TStrItem));
- end;
-
- function NewStrItem(const AString: string; AObject: TObject): PStrItem;
- var
- p: PStrItem;
- c: pchar;
- begin
- GetMem(p, SizeOf(TStrItem));
- GetMem(c, Length(AString) + 1);
- StrPCopy(c, AString);
- p^.FObject := AObject;
- p^.FString := c;
- Result := p;
- end;
-
- function TQDBStringList.Add(const S: string): longint;
- begin
- if not Sorted then
- Result := FList.Count
- else
- if Find(S, Result) then
- Error(SDuplicateKey, 0);
- FList.Insert(Result, NewStrItem(S, nil));
- end;
-
- function TQDBStringList.AddObject(const S: string; AObject: TObject):
- longint;
- begin
- if not Sorted then
- Result := FList.Count
- else
- if Find(S, Result) then
- Error(SDuplicateKey, 0);
- FList.Insert(Result, NewStrItem(S, AObject));
- end;
-
- procedure TQDBStringList.Clear;
- var
- i: longint;
- begin
- for i := 1 to FList.Count do
- begin
- DisposeStrItem(FList[i - 1]);
- FList[i - 1] := nil;
- end;
- FList.Clear;
- end;
-
- procedure TQDBStringList.Delete(Index: longint);
- begin
- DisposeStrItem(FList[Index]);
- FList.Delete(Index);
- end;
-
- procedure TQDBStringList.Error(const ErrMsg: string; Data: longint);
- var
- StackTop: record
- end;
- Stack: record
- BPorEBP: integer; { 16 bit: BP, 32 bit: EBP }
- ReturnAddress: pointer;
- end absolute StackTop;
- begin
- raise EQDBListError.CreateFmt(ErrMsg, [Data])at Stack.ReturnAddress;
- end;
-
- procedure TQDBStringList.Exchange(Index1, Index2: longint);
- begin
- FList.Exchange(Index1, Index2);
- end;
-
- function TQDBStringList.Find(const S: string; var Index: longint): boolean;
- var
- L, H, i, c: longint;
- begin
- Result := false;
- L := 0;
- H := FList.Count - 1;
- while L <= H do
- begin
- i := (L + H) shr 1;
- if CaseSensitive then
- c := AnsiCompareStr(StrPas(PStrItem(FList[i])^.FString), S)
- else
- c := AnsiCompareText(StrPas(PStrItem(FList[i])^.FString), S);
- if c < 0 then
- L := i + 1
- else
- begin
- H := i - 1;
- if c = 0 then
- begin
- Result := true;
- L := i;
- end;
- end;
- end;
- Index := L;
- end;
-
- function TQDBStringList.Get(Index: longint): string;
- begin
- Result := StrPas(PStrItem(FList[Index])^.FString);
- end;
-
- function TQDBStringList.GetCapacity: longint;
- begin
- Result := FList.Capacity;
- end;
-
- function TQDBStringList.GetCount: longint;
- begin
- Result := FList.Count;
- end;
-
- function TQDBStringList.GetObject(Index: longint): TObject;
- begin
- Result := PStrItem(FList[Index])^.FObject;
- end;
-
- procedure TQDBStringList.Put(Index: longint; const S: string);
- var
- p: PStrItem;
- begin
- { get the old str item }
- p := FList[Index];
- { create and assign the new str item }
- FList[Index] := NewStrItem(S, p^.FObject);
- { get rid of the old one }
- DisposeStrItem(p);
- end;
-
- procedure TQDBStringList.PutObject(Index: longint; AObject: TObject);
- var
- p: PStrItem;
- begin
- p := FList[Index];
- FList[Index] := NewStrItem(Strings[Index], AObject);
- DisposeStrItem(p);
- end;
-
- procedure TQDBStringList.Reverse;
- { the QDB index gets read in in reverse order ... this just reverses that }
- { since it leaves the items in sorted order it is safe to set sorted to true }
- var
- n, m: longint;
- begin
- if Sorted then
- exit;
- n := 1;
- m := FList.Count;
- while n < m do
- begin
- FList.Exchange(n - 1, m - 1);
- inc(n);
- dec(m);
- end;
- FSorted := true;
- end;
-
- procedure TQDBStringList.SetCapacity(NewCapacity: longint);
- begin
- FList.Capacity := NewCapacity;
- end;
-
- procedure TQDBStringList.SetCaseSensitive(Value: boolean);
- var
- n: longint;
- begin
- { if the list is empty it's easy ...}
- if Count = 0 then
- FCaseSensitive := Value
- else
- begin
- if FCaseSensitive <> Value then
- begin
- FCaseSensitive := Value;
- { if we are going from sensitive to insensitive we have extra work }
- if not FCaseSensitive then
- begin
- {check for duplicates and delete them }
- n := Count - 1;
- while n > 0 do
- begin
- if AnsiCompareText(Get(n - 1), Get(n)) = 0 then
- Delete(n);
- dec(n);
- end;
- end;
- end;
- end;
- end;
-
- procedure TQDBStringList.SetSorted(Value: boolean);
- begin
- if FSorted <> Value then
- FSorted := Value;
- end;
-
- { ******* Index and cache records ******* }
-
- type
- TIndex = class { each index object points to variable length data... }
- Pos: TFilePos; { it's location in the data file }
- Len: TDataIndex; { and it's length }
- Ext: TFlagSet; { additional flags }
- end;
-
- type
- TCache = class
- FAge: longint;
- Stream: TMemoryStream; { the stream holds the data }
- public
- constructor Create(Data: TStream; Age: longint);
- destructor Destroy; override;
- end;
-
- { TCache }
-
- constructor TCache.Create(Data: TStream; Age: longint);
- begin
- inherited Create;
- FAge := Age;
- Stream := TMemoryStream.Create;
- Stream.LoadFromStream(Data);
- Data.Seek(0, 0);
- Stream.Seek(0, 0);
- end;
-
- destructor TCache.Destroy;
- begin
- Stream.Free;
- inherited Destroy;
- end;
-
- { TIndexList }
-
- destructor TIndexList.Destroy;
- begin
- EmptyAndClear;
- inherited Destroy;
- end;
-
- procedure TIndexList.EmptyAndClear;
- begin
- while Count > 0 do
- begin
- TIndex(Objects[Count - 1]).Free;
- Delete(Count - 1);
- end;
- end;
-
- { TCacheList }
-
- constructor TCacheList.Create;
- begin
- inherited Create;
- CaseSensitive := true;
- Sorted := true;
- FCurrentSize := 0;
- FMaximumSize := 64 * 1024; { default cache size 64K }
- FSuccesses := 0;
- FAttempts := 0;
- FDisposals := 0;
- FOldest := 0;
- end;
-
- destructor TCacheList.Destroy;
- begin
- Flush;
- inherited Destroy;
- end;
-
- procedure TCacheList.Fetch(Stream: TStream; Place: longint);
- var
- CacheRec: TCache;
- begin
- CacheRec := TCache(Objects[Place]);
- CacheRec.Stream.SaveToStream(Stream);
- CacheRec.Stream.Seek(0, 0);
- { promote item in age list }
- inc(FOldest);
- CacheRec.FAge := FOldest;
- { update statistics }
- inc(FSuccesses);
- inc(FAttempts);
- end;
-
- procedure TCacheList.Flush;
- { clear the item cache }
- begin
- while Count > 0 do
- begin
- TCache(Objects[Count - 1]).Free;
- Delete(Count - 1);
- end;
- FCurrentSize := 0;
- FSuccesses := 0;
- FAttempts := 0;
- FDisposals := 0;
- FOldest := 0;
- end;
-
- function TCacheList.GetFrequency: integer;
- { superceded by Statistics }
- begin
- if FAttempts <> 0 then
- Result := Round((100.0 * FSuccesses) / FAttempts)
- else
- Result := 0;
- end;
-
- procedure TCacheList.MakeSpace;
- { remove the oldest item from the cache ... }
- var
- oldest: longint;
- oldestindex: longint;
- n: longint;
- oldestcache: TCache;
- begin
- if Count < 1 then
- exit;
- oldest := maxint;
- oldestindex := 0;
- for n := 0 to Count - 1 do
- begin
- oldestcache := TCache(Objects[n]);
- if oldestcache.FAge < oldest then
- begin
- oldest := oldestcache.FAge;
- oldestindex := n;
- end;
- end;
- oldestcache := TCache(Objects[oldestindex]);
- dec(FCurrentSize, oldestcache.Stream.size);
- oldestcache.Free;
- Delete(oldestindex);
- inc(FDisposals);
- end;
-
- procedure TCacheList.Remove(Key: TKey);
- { remove the specified item from the cache }
- var
- CacheN: longint;
- begin
- if Find(Key, CacheN) then
- begin
- dec(FCurrentSize, TCache(Objects[CacheN]).Stream.size);
- TCache(Objects[CacheN]).Free;
- Delete(CacheN);
- end
- end;
-
- procedure TCacheList.SetFrequency(Value: integer);
- begin
- FSuccesses := 0;
- FAttempts := 0;
- FDisposals := 0;
- end;
-
- procedure TCacheList.SetSize(Value: longint);
- { note: changing the cache size empties it }
- begin
- Flush;
- FMaximumSize := Value;
- end;
-
- procedure TCacheList.Statistics(var MaxSize, CurSize, CurLen, HitRatio,
- DropRatio: longint);
- begin
- if FAttempts = 0 then
- begin
- MaxSize := FMaximumSize;
- CurSize := 0;
- CurLen := 0;
- HitRatio := 0;
- DropRatio := 0;
- end
- else
- begin
- MaxSize := FMaximumSize;
- CurSize := FCurrentSize;
- CurLen := Count;
- HitRatio := (FSuccesses * 100) div FAttempts;
- DropRatio := (FDisposals * 100) div FAttempts;
- end;
- end;
-
- procedure TCacheList.Store(Stream: TStream; Key: TKey);
- { add an item to the cache, making space if needed via MakeSpace }
- var
- CacheRec: TCache;
- begin
- { we don't try to cache items bigger than the limit }
- if Stream.size >= FMaximumSize then
- exit;
- { make room for the new entry by removing as many old ones as needed }
- while FCurrentSize + Stream.size > FMaximumSize do
- MakeSpace;
- inc(FOldest);
- try
- CacheRec := TCache.Create(Stream, FOldest);
- except
- exit;
- end;
- try
- AddObject(Key, CacheRec);
- except
- CacheRec.Free;
- exit;
- end;
- inc(FCurrentSize, CacheRec.Stream.size);
- inc(FAttempts);
- end;
-
- { TBFStream }
-
- constructor TBFStream.Create(const FileName: string; Mode: word; BufferSize:
- longint);
- { if BufferSize is -1 we try to get a buffer big enough for the whole file }
- begin
- inherited Create(FileName, Mode);
- if BufferSize = -1 then
- begin
- BufferSize := inherited Seek(0, 2);
- inherited Seek(0, 0);
- end;
- {need to make sure we have zero byte at the end of the buffer}
- BufLen := GetBuffer(pointer(Buffer), BufferSize) - 1;
- IsDirty := false;
- ResetBuffer;
- end;
-
- destructor TBFStream.Destroy;
- begin
- ResetBuffer;
- FreeMem(Buffer, BufLen + 1);
- inherited Destroy;
- end;
-
- procedure TBFStream.AdjustBuffer;
- begin
- if IsDirty then
- begin
- Write(Buffer^, BufferPos - Buffer);
- IsDirty := false;
- end;
- FillChar(Buffer^, BufLen + 1, #0);
- inherited Seek(BufferPos - Buffer - BytesRead, 1);
- end;
-
- function TBFStream.GetKey(var k: TKey): boolean;
- begin
- Result := false;
- if (BufferPos - Buffer + StrLen(BufferPos) + 1 > BytesRead) then
- begin
- if (BytesRead < BufLen) then
- exit
- else
- begin
- AdjustBuffer;
- BufferPos := Buffer;
- BytesRead := Read(Buffer^, BufLen);
- Result := GetKey(k);
- end;
- end
- else
- begin
- k := StrPas(BufferPos);
- inc(BufferPos, Length(k) + 1);
- Result := true;
- end;
- end;
-
- function TBFStream.GetLongint(var L: longint): boolean;
- begin
- Result := false;
- if (BufferPos - Buffer + SizeOf(L) > BytesRead) then
- begin
- if (BytesRead < BufLen) then
- exit
- else
- begin
- AdjustBuffer;
- BufferPos := Buffer;
- BytesRead := Read(Buffer^, BufLen);
- Result := GetLongint(L);
- end;
- end
- else
- begin
- Move(BufferPos^, L, SizeOf(L));
- inc(BufferPos, SizeOf(L));
- Result := true;
- end;
- end;
-
- procedure TBFStream.PutKey(const k: TKey);
- begin
- if (BufferPos - Buffer + Length(k) + 1 > BufLen) then
- begin
- ResetBuffer;
- BufferPos := Buffer;
- end;
- StrPCopy(BufferPos, k);
- inc(BufferPos, Length(k) + 1);
- IsDirty := true;
- end;
-
- procedure TBFStream.PutLongint(const L: longint);
- begin
- if (BufferPos - Buffer + SizeOf(L) > BufLen) then
- begin
- ResetBuffer;
- BufferPos := Buffer;
- end;
- Move(L, BufferPos^, SizeOf(L));
- inc(BufferPos, SizeOf(L));
- IsDirty := true;
- end;
-
- procedure TBFStream.ResetBuffer;
- begin
- if IsDirty then
- begin
- Write(Buffer^, BufferPos - Buffer);
- IsDirty := false;
- end;
- FillChar(Buffer^, BufLen + 1, #0);
- BufferPos := Buffer + BufLen;
- BytesRead := BufLen;
- end;
-
- function TBFStream.Seek(Offset: longint; Origin: word): longint;
- begin
- ResetBuffer;
- Result := inherited Seek(Offset, Origin);
- end;
-
- { ******* Utility routine ******* }
-
- function TempLocationValid: boolean;
- var
- tfl: string;
- L: integer;
- begin
- tfl := QDBTempFileLocation;
- Result := false;
- if tfl <> '' then
- begin
- tfl := ExpandFileName(tfl);
- L := Length(tfl);
- if tfl[L] = '\' then
- begin
- if tfl[L - 1] <> ':' then
- Delete(QDBTempFileLocation, Length(QDBTempFileLocation), 1);
- tfl := tfl + 'nul';
- end
- else
- tfl := tfl + '\nul';
- Result := FileExists(tfl);
- end;
- end;
-
- {$IFNDEF WIN32}
- const
- MAX_PATH = 255;
- {$ENDIF}
-
- function TempFileName(Prefix: string): string;
- { returns a unique name for a temp file }
- const
- TmpStrLen = MAX_PATH;
- var
- TmpName: pchar;
- TmpPath: pchar;
- {$IFNDEF WIN32}
- Dummy: pchar;
- {$ENDIF}
- begin
- Result := '';
- GetMem(TmpName, TmpStrLen);
- try
- FillChar(TmpName^, TmpStrLen, #0);
- {$IFDEF WIN32}
- GetMem(TmpPath, TmpStrLen);
- try
- FillChar(TmpPath^, TmpStrLen, #0);
- if TempLocationValid then
- StrPCopy(TmpPath, ExpandFileName(QDBTempFileLocation))
- else
- GetTempPath(TmpStrLen, TmpPath);
- GetTempFileName(TmpPath, pchar(Prefix), 0, TmpName);
- Result := TmpName;
- finally
- FreeMem(TmpPath, TmpStrLen);
- end;
- {$ELSE}
- GetMem(Dummy, Length(Prefix) + 1);
- try
- GetTempFileName(#0, StrPCopy(Dummy, Prefix), 0, TmpName);
- Result := StrPas(TmpName);
- finally
- FreeMem(Dummy, Length(Prefix) + 1);
- end;
- {$ENDIF}
- finally
- FreeMem(TmpName, TmpStrLen);
- end;
- end;
-
- procedure RenameOrMoveFile(const SrcFileName, DstFileName: string);
- { if src and dst are on the same drive rename will work }
- { if not we have to pysically move the file }
- var
- FSrc: TFileStream;
- FDst: TFileStream;
- begin
- {first get rid of the dst file }
- SysUtils.DeleteFile(DstFileName);
- { if a rename doesn't work we have to copy }
- if not RenameFile(SrcFileName, DstFileName) then
- begin
- FDst := TFileStream.Create(DstFileName, fmCreate);
- try
- FSrc := TFileStream.Create(SrcFileName, fmOpenRead);
- try
- FDst.CopyFrom(FSrc, FSrc.size);
- finally
- FSrc.Free;
- end;
- finally
- FDst.Free;
- end;
- SysUtils.DeleteFile(SrcFileName);
- end;
- end;
-
- { TTempBFStream }
-
- const
- TmpFilePrefix = 'QDT';
-
- constructor TTempBFStream.Create(const OldFileName: string);
- begin
- TmpFileName := TempFileName(TmpFilePrefix);
- if TmpFileName = '' then
- raise EQDBIndexError.CreateFmt(STempFile, [FOldFileName]);
- inherited Create(TmpFileName, fmCreate or fmShareExclusive, 32 * 1024);
- FOldFileName := OldFileName;
- end;
-
- destructor TTempBFStream.Destroy;
- begin
- inherited Destroy;
- if TmpFileName <> '' then
- RenameOrMoveFile(TmpFileName, FOldFileName);
- TmpFileName := '';
- FOldFileName := '';
- end;
-
- { GREP code ... a cut-down version of code graciously supplied by
- Gerald Nunn ... from his GExperts Delphi add-in
- (http://www.amano-blick.com/~gnunn/GExperts.htm). Any problems
- with this code are mine rather than his. }
-
- const
- opCHAR = 1;
- opBOL = 2;
- opEOL = 3;
- opANY = 4;
- opCLASS = 5;
- opNCLASS = 6;
- opSTAR = 7;
- opBOW = 8; {opPLUS = 8;}
- opEOW = 9; {opMINUS = 9;}
- opALPHA = 10;
- opDIGIT = 11;
- opNALPHA = 12;
- opPUNCT = 13;
- opRANGE = 14;
- opENDPAT = 15;
-
- function LoCase(ch: char): char;
- begin
- if (ch >= 'A') and (ch <= 'Z') then
- inc(ch, 32);
- Result := ch;
- end;
-
- function GrepMatch(const S, Pattern: string; CaseSensitive, WholeWord: boolean
- ): boolean;
- var
- L: integer;
- FixBOL: boolean; { beginning of line }
- FixBOW: boolean; { beginning of word }
- FString: pchar;
- FStrLen: integer;
- FPattern: pchar;
- PatternOK: boolean;
-
- procedure CompilePattern(Source: string);
- var
- lp: integer; {Last Pattern Pointer}
- c: integer; {Current Character}
-
- procedure Store(ch: char);
- begin
- if not CaseSensitive then
- FPattern[lp] := LoCase(ch)
- else
- FPattern[lp] := ch;
- inc(lp);
- end;
-
- procedure cclass;
- var
- cstart: integer;
- begin
- cstart := c;
- inc(c);
- if Source[c] = '^' then
- Store(char(opNCLASS))
- else
- Store(char(opCLASS));
-
- while (c <= Length(Source)) and (Source[c] <> ']') do
- begin
- if (Source[c] = '\') and (c < Length(Source)) and (Source[c + 1
- ] = '\') then
- begin
- Store(Source[c + 2]);
- inc(c, 3);
- end
- else
- if (Source[c] = '-') and (c - cstart > 1) and (Source[c + 1]
- <>
- ']') and (c < Length(Source)) then
- begin
- dec(lp, 2);
- Store(char(opRANGE));
- Store(Source[c - 1]);
- Store(Source[c + 1]);
- inc(c, 2);
- end
- else
- begin
- Store(Source[c]);
- inc(c);
- end;
- end;
- if (Source[c] <> ']') or (c > Length(Source)) then
- begin
- PatternOK := false;
- exit;
- end;
- inc(c);
- end;
-
- begin
- try
- c := 1;
- lp := 0;
- while c <= Length(Source) do
- begin
- case Source[c] of
- '^': { beginning of line }
- begin
- if c = 1 then
- FixBOL := true
- else
- begin
- PatternOK := false;
- exit;
- end;
- inc(c);
- end;
- '%': { beginning of word }
- begin
- if c = 1 then
- FixBOW := true
- else
- begin
- PatternOK := false;
- exit;
- end;
- inc(c);
- end;
- '$': { end of line }
- begin
- if c <> Length(Source) then
- begin
- PatternOK := false;
- exit;
- end;
- Store(char(opEOL));
- inc(c);
- end;
- '&': { end of word }
- begin
- if c <> Length(Source) then
- begin
- PatternOK := false;
- exit;
- end;
- Store(char(opEOW));
- inc(c);
- end;
- '.':
- begin
- Store(char(opANY));
- inc(c);
- end;
- '[':
- cclass;
- ':':
- begin
- if c < Length(Source) then
- begin
- case UpCase(Source[c + 1]) of
- 'A':
- Store(char(opALPHA));
- 'D':
- Store(char(opDIGIT));
- 'N':
- Store(char(opNALPHA));
- ' ':
- Store(char(opPUNCT));
- else
- begin
- Store(char(opENDPAT));
- PatternOK := false;
- exit;
- end;
- end;
- inc(c, 2);
- end;
- end;
- '\':
- begin
- if c < Length(Source) then
- if Source[c + 1] = '\' then
- begin
- Store(char(opCHAR));
- Store(Source[c + 2]);
- inc(c, 3);
- end
- else
- begin
- Store(char(opCHAR));
- Store(Source[c]);
- inc(c);
- end
- else
- begin
- Store(char(opCHAR));
- Store(Source[c]);
- inc(c);
- end;
- end;
- else
- begin
- Store(char(opCHAR));
- Store(Source[c]);
- inc(c);
- end;
- end;
- end;
- finally
- Store(char(opENDPAT));
- Store(#0);
- end;
- end;
-
- function PatternMatch: boolean;
- var
- L, p: integer; {line and pattern pointers}
- op: integer; {Pattern operation}
- LinePos: integer;
-
- function IsFound: boolean;
- var
- S, E: integer;
- begin
- Result := false;
- if WholeWord then
- begin
- S := LinePos - 2;
- E := L;
- if (S > 0) then
- if (LoCase(FString[S]) >= 'a') and (LoCase(FString[S]) <=
- 'z') then
- exit;
- if (FString[E] <> #0) then
- if (LoCase(FString[E]) >= 'a') and (LoCase(FString[E]) <=
- 'z') then
- exit;
- end;
- if FixBOL and (LinePos <> 1) then
- exit;
- if (FixBOW) and not ((LinePos = 1) or (FString[LinePos - 2] = ' ')
- or (FString[LinePos - 2] <= #64)) then
- exit;
- Result := true;
- end;
-
- begin
- Result := false;
- if not PatternOK then
- exit;
- if FString[0] = #0 then
- exit;
- if integer(FPattern[0]) = opENDPAT then
- exit;
- if not CaseSensitive then
- StrLower(FString);
-
- LinePos := 0;
-
- {Don't bother pattern matching if first search is opCHAR, just go to first match directly}
- {Results in about a 5% to 10% speed increase}
- if (integer(FPattern[0]) = opCHAR) and not CaseSensitive then
- while (FPattern[1] <> FString[LinePos]) and (FString[LinePos] <>
- #0) do
- inc(LinePos);
-
- while FString[LinePos] <> #0 do
- begin
- L := LinePos;
- p := 0;
- op := integer(FPattern[p]);
- while (op <> opENDPAT) do
- begin
- case op of
- opCHAR:
- begin
- if not (FString[L] = FPattern[p + 1]) then
- Break;
- inc(p, 2);
- end;
- opEOL:
- begin
- if L = FStrLen then
- begin
- inc(LinePos);
- Result := IsFound;
- end;
- exit;
- end;
- opEOW:
- begin
- if (L = FStrLen) or (FString[L] = ' ') or (FString[L] <
- #64) then
- begin
- inc(LinePos);
- Result := IsFound;
- end;
- exit;
- end;
- opANY:
- begin
- if (FString[L] = #13) or (FString[L] = #10) or (FString[L
- ] = #0) then
- Break;
- inc(p);
- end;
- opCLASS:
- begin
- inc(p);
- {Compare letters to find a match}
- while (FPattern[p] > #15) and (FPattern[p] <> FString[L]) do
- inc(p);
- {Was a match found?}
- if (FPattern[p] <= #15) then
- Break;
- {move FPattern pointer to next opcode}
- while (FPattern[p] > #15) do
- inc(p);
- end;
- opNCLASS:
- begin
- inc(p);
- {Compare letters to find a match}
- while (FPattern[p] > #15) and (FPattern[p] <> FString[L]) do
- inc(p);
- if (FPattern[p] > #15) then
- Break;
- end;
- opALPHA:
- begin
- if (LoCase(FString[L]) < 'a') or (LoCase(FString[L]) >
- 'z') then
- Break;
- inc(p);
- end;
- opDIGIT:
- begin
- if (FString[L] < '0') or (FString[L] > '9') then
- Break;
- inc(p);
- end;
- opNALPHA:
- begin
- if (LoCase(FString[L]) > 'a') or (LoCase(FString[L]) <
- 'z') then
- Break;
- inc(p);
- end;
- opPUNCT:
- begin
- if (FString[L] = ' ') or (FString[L] > #64) then
- Break;
- inc(p);
- end;
- opRANGE:
- begin
- if (FString[L] < FPattern[p + 1]) or (FString[L] >
- FPattern[p + 2]) then
- Break;
- inc(p, 3);
- end;
- else
- inc(p);
- end; {End Case}
- op := integer(FPattern[p]);
- inc(L);
- end; {End While op<>opENDPAT}
- inc(LinePos);
- if op = opENDPAT then
- Result := IsFound;
- end; {While FString[LinePos]<>#0}
- end;
-
- begin
- L := Length(S) + 1;
- FString := StrAlloc(L);
- FillChar(FString^, L, #0);
- FString := StrPCopy(FString, S);
- FStrLen := StrLen(FString);
- FPattern := StrAlloc(512);
- FixBOL := false;
- FixBOW := false;
- PatternOK := true;
-
- CompilePattern(Pattern);
- if PatternOK then
- Result := PatternMatch
- else
- Result := true;
-
- StrDispose(FString);
- StrDispose(FPattern);
- end;
-
- { TQDB }
-
- constructor TQDB.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- try
- Index := TIndexList.Create;
- Index.Sorted := true;
- Cache := TCacheList.Create;
- Admin := TIndexList.Create;
- Admin.Sorted := true;
- SetReady(false);
- FItemIndex := 0;
- FAdminIndex := 0;
- FKey := '';
- FCount := Index.Count;
- FReadOnly := false;
- FFrontWild := '<';
- FBackWild := '>';
- FVersion := FileVersion;
- FAuthor := AuthorInfo;
- FUpdating := 0;
- FSaveOnClose := true;
- FExpandedFileNames := true;
- UseGrepMatch := false;
- Restructuring := false;
- except
- Index.Free;
- Cache.Free;
- Admin.Free;
- raise;
- end;
- end;
-
- destructor TQDB.Destroy;
- begin
- if Live and (FFileName <> '') then
- CloseQDB;
- Index.Free;
- Cache.Free;
- Admin.Free;
- FItemIndex := 0;
- inherited Destroy;
- end;
-
- procedure TQDB.AboutToKill(var OK: boolean);
- begin
- if Assigned(FBeforeKill) then
- FBeforeKill(Self, OK);
- end;
-
- procedure TQDB.AboutToOverWrite(var OK: boolean);
- begin
- if Assigned(FBeforeOverWrite) then
- FBeforeOverWrite(Self, OK);
- end;
-
- procedure TQDB.Add(Stream: TStream; Key: TKey);
- { add an item to the file -- this is now the method of choice }
- var
- IndRec: TIndex;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- Stream.Seek(0, 0);
- if ReadOnly then
- CannotChange
- else
- begin
- try
- IndRec := TIndex.Create;
- except
- IndexError(SIndexAdd);
- end;
- IndRec.Pos := QDBFile.Seek(0, 2);
- if IndRec.Pos < 0 then
- FileError(-1, SCorrupt);
- try
- FItemIndex := Index.AddObject(Key, IndRec);
- except
- IndRec.Free;
- IndexError(SDuplicateKey);
- end;
- FKey := GetKey(FItemIndex);
- Cache.Store(Stream, Index.Strings[FItemIndex]);
- if Compression then
- begin
- TIndex(Index.Objects[FItemIndex]).Len := SquashStream(Stream, QDBFile
- );
- TIndex(Index.Objects[FItemIndex]).Ext := TIndex(Index.Objects[
- FItemIndex]).Ext + [IsCompressed];
- end
- else
- begin
- TIndex(Index.Objects[FItemIndex]).Len := QDBFile.CopyFrom(Stream,
- Stream.size);
- TIndex(Index.Objects[FItemIndex]).Ext := TIndex(Index.Objects[
- FItemIndex]).Ext - [IsCompressed];
- end;
- FCount := Index.Count;
- IsDirty := true;
- Added;
- if (FUpdating = 0) then
- Navigate;
- end;
- Stream.Seek(0, 0);
- end;
-
- procedure TQDB.Added;
- begin
- if Assigned(FOnAdded) then
- FOnAdded(Self);
- end;
-
- procedure TQDB.AddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey);
- { add an item to the file -- if you can, use Add instead }
- var
- TmpStream: TMemoryStream;
- begin
- TmpStream := TMemoryStream.Create;
- try
- TmpStream.Write(ItemPtr^, ItemLen);
- Add(TmpStream, Key);
- finally
- TmpStream.Free;
- end;
- end;
-
- procedure TQDB.AddStreamItem(Stream: TStream; Key: TKey);
- { add an item to the file -- provided for compatibility }
- { -- use Add instead }
- begin
- Add(Stream, Key);
- end;
-
- procedure TQDB.AdminAddItem(ItemPtr: pointer; ItemLen: TDataIndex; Key: TKey
- );
- var
- IndRec: TIndex;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly then
- CannotChange
- else
- begin
- try
- IndRec := TIndex.Create;
- except
- IndexError(SIndexAdd);
- end;
- IndRec.Pos := QDBFile.Seek(0, 2);
- if IndRec.Pos < 0 then
- FileError(-1, SCorrupt);
- IndRec.Len := ItemLen;
- IndRec.Ext := IndRec.Ext + [IsAdminItem];
- try
- FAdminIndex := Admin.AddObject(Key, IndRec);
- except
- IndRec.Free;
- IndexError(SDuplicateKey);
- end;
- if QDBFile.Write(ItemPtr^, ItemLen) <> ItemLen then
- IndexError(SDataAdd);
- IsDirty := true;
- end;
- end;
-
- procedure TQDB.AdminChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
- var
- ThisKey: TKey;
- IndRec: TIndex;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if Admin.Count < 1 then
- NoData
- else
- if ReadOnly then
- CannotChange
- else
- begin
- ThisKey := Admin.Strings[FAdminIndex];
- if ItemLen <= AdminItemSize then
- begin {just write on top of the old}
- with TIndex(Admin.Objects[FAdminIndex]) do
- begin
- QDBFile.Seek(Pos, 0);
- Len := ItemLen;
- end;
- end
- else
- begin
- TIndex(Admin.Objects[FAdminIndex]).Free;
- Admin.Delete(FAdminIndex);
- try
- IndRec := TIndex.Create;
- except
- IndexError(SIndexAdd);
- end;
- IndRec.Pos := QDBFile.Seek(0, 2);
- if IndRec.Pos < 0 then
- FileError(-1, SDataAdd);
- IndRec.Len := ItemLen;
- IndRec.Ext := IndRec.Ext + [IsAdminItem];
- try
- FAdminIndex := Admin.AddObject(ThisKey, IndRec);
- except
- IndRec.Free;
- raise
- end;
- end;
- if QDBFile.Write(ItemPtr^, ItemLen) <> ItemLen then
- FileError(-1, SDataAdd);
- IsDirty := true;
- end;
- end;
-
- procedure TQDB.AdminClear(StartOfKey: TKey);
- var
- TmpList: TStringList;
- i: integer;
- begin
- if StartOfKey = '' then
- begin
- Admin.EmptyAndClear;
- FAdminIndex := 0;
- end
- else
- begin
- TmpList := TStringList.Create;
- try
- AdminKeys(TmpList, StartOfKey);
- for i := 0 to TmpList.Count - 1 do
- AdminDelete(TmpList[i]);
- finally
- TmpList.Free;
- end;
- end;
- end;
-
- function TQDB.AdminCount: TItemIndex;
- { nb not a property like TQDB.Count }
- begin
- Result := Admin.Count;
- end;
-
- procedure TQDB.AdminDelete(Key: TKey);
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly then
- CannotChange
- else
- if not AdminExactMatch(Key) then
- raise EQDBBadKey.Create(SBadKey)
- else
- begin
- TIndex(Admin.Objects[FAdminIndex]).Free;
- Admin.Delete(FAdminIndex);
- if FAdminIndex > 0 then
- FAdminIndex := FAdminIndex - 1;
- IsDirty := true;
- end;
- end;
-
- function TQDB.AdminExactMatch(Key: TKey): boolean;
- { generally you will know what items are stored -- use }
- { this function to locate the key you want }
- var
- n: TItemIndex;
- begin
- Result := Admin.Find(Key, n);
- if Result then
- FAdminIndex := n;
- end;
-
- function TQDB.AdminGetBoolean(Key: TKey): boolean;
- var
- Buffer: pointer;
- BufLen: longint;
- begin
- Result := false;
- if AdminExactMatch(Key) then
- begin
- BufLen := AdminItemSize;
- if BufLen = SizeOf(boolean) then
- begin
- GetMem(Buffer, BufLen);
- try
- AdminGetItem(Buffer);
- Result := Byte(Buffer^) = 1;
- finally
- FreeMem(Buffer, BufLen);
- end;
- end;
- end
- else
- raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
- end;
-
- function TQDB.AdminGetInteger(Key: TKey): longint;
- var
- Buffer: pointer;
- BufLen: longint;
- begin
- Result := 0;
- if AdminExactMatch(Key) then
- begin
- BufLen := AdminItemSize;
- if BufLen = SizeOf(longint) then
- begin
- GetMem(Buffer, BufLen);
- try
- AdminGetItem(Buffer);
- Result := longint(Buffer^);
- finally
- FreeMem(Buffer, BufLen);
- end;
- end;
- end
- else
- raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
- end;
-
- procedure TQDB.AdminGetItem(ItemPtr: pointer);
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if Admin.Count > 0 then
- begin
- try
- QDBFile.Seek(TIndex(Admin.Objects[FAdminIndex]).Pos, 0);
- QDBFile.Read(ItemPtr^, TIndex(Admin.Objects[FAdminIndex]).Len);
- except
- IndexError(SCorrupt);
- end;
- end
- else
- NoData;
- end;
-
- function TQDB.AdminGetString(Key: TKey): string;
- var
- Buffer: pointer;
- BufLen: longint;
- begin
- Result := '';
- if AdminExactMatch(Key) then
- begin
- BufLen := AdminItemSize;
- {$IFNDEF WIN32}
- if BufLen > 255 then
- BufLen := 255;
- {$ENDIF}
- if BufLen > 0 then
- begin
- GetMem(Buffer, BufLen);
- try
- AdminGetItem(Buffer);
- Result := StrPas(pchar(Buffer));
- finally
- FreeMem(Buffer, BufLen);
- end;
- end;
- end
- else
- raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
- end;
-
- function TQDB.AdminItemSize: TDataIndex;
- begin
- if Admin.Count < 1 then
- begin
- Result := 0;
- NoData;
- end
- else
- Result := TIndex(Admin.Objects[FAdminIndex]).Len;
- end;
-
- function TQDB.AdminKeyExists(Key: TKey): boolean;
- begin
- Result := AdminExactMatch(Key);
- end;
-
- function TQDB.AdminKeys(Keys: TStrings; StartOfKey: TKey): longint;
- { makes a list of all the Admin keys }
- var
- ThisItem: TItemIndex;
- ThisKey: TKey;
- Len: longint;
- begin
- TStringList(Keys).Clear;
- for ThisItem := 1 to Admin.Count do
- begin
- ThisKey := Admin.Strings[ThisItem - 1];
- Len := Length(StartOfKey);
- if Copy(ThisKey, 1, Len) = StartOfKey then
- Keys.Add(ThisKey);
- end;
- Result := Keys.Count;
- end;
-
- procedure TQDB.AdminSetBoolean(Key: TKey; b: boolean);
- begin
- if AdminExactMatch(Key) then
- AdminChangeItem(@b, SizeOf(b))
- else
- AdminAddItem(@b, SizeOf(b), Key);
- end;
-
- procedure TQDB.AdminSetInteger(Key: TKey; n: longint);
- begin
- if AdminExactMatch(Key) then
- AdminChangeItem(@n, SizeOf(n))
- else
- AdminAddItem(@n, SizeOf(n), Key);
- end;
-
- procedure TQDB.AdminSetString(Key: TKey; const S: string);
- var
- p: pchar;
- begin
- p := StrAlloc(Length(S) + 1);
- try
- StrPCopy(p, S);
- if AdminExactMatch(Key) then
- AdminChangeItem(p, Length(S) + 1)
- else
- AdminAddItem(p, Length(S) + 1, Key);
- finally
- StrDispose(p);
- end;
- end;
-
- procedure TQDB.AssignKeyList(Keys: TStrings);
- { Copies the in-memory list of keys to the Keys parameter. }
- { Items that are filtered out are not included. }
- var
- ThisItem: TItemIndex;
- ThisKey: TKey;
- WasSorted: boolean;
- begin
- TStringList(Keys).Clear;
- { TStrings has no sort method but TStringList does }
- if Keys is TStringList then
- begin
- WasSorted := TStringList(Keys).Sorted;
- TStringList(Keys).Sorted := false;
- end;
- MonitorSetup(Index.Count, prKeyList);
- for ThisItem := 1 to Index.Count do
- begin
- MonitorUpdate(ThisItem);
- if Filtered then
- begin
- ThisKey := GetKey(ThisItem - 1);
- if Matches(ThisKey, FFilter) then
- TStringList(Keys).Add(ThisKey);
- end
- else
- TStringList(Keys).Add(GetKey(ThisItem - 1));
- end;
- if Keys is TStringList then
- TStringList(Keys).Sorted := WasSorted;
- MonitorZero;
- end;
-
- procedure TQDB.BeginUpdate;
- begin
- inc(FUpdating);
- end;
-
- procedure TQDB.CacheFlush;
- begin
- Cache.Flush;
- end;
-
- procedure TQDB.CacheStatistics(var MaxSize, CurSize, CurLen, HitRatio, DropRatio
- : longint);
- begin
- Cache.Statistics(MaxSize, CurSize, CurLen, HitRatio, DropRatio);
- end;
-
- procedure TQDB.DoCancel;
- begin
- if not AutoEdit then
- FEditing := false;
- FInserting := false;
- UpdateNavigator;
- end;
-
- procedure TQDB.Cancel;
- begin
- if Assigned(FBeforeCancel) then
- FBeforeCancel(Self);
- DoCancel;
- if Assigned(FAfterCancel) then
- FAfterCancel(Self);
- end;
-
- procedure TQDB.CannotChange;
- { If a warning handler has not been assigned an exception is raised }
- { To silence exceptions assign at least an empty handler }
- begin
- if Assigned(FWarnReadOnly) then
- FWarnReadOnly(Self)
- else
- raise EQDBReadOnly.CreateFmt(SReadOnly, [FFileName]);
- end;
-
- procedure TQDB.Change(Stream: TStream);
- { Change the contents of the current stream item }
- { This is the change method of choice -- use it }
- var
- ThisKey: TKey;
- IndRec: TIndex;
- TmpStream: TMemoryStream;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if Index.Count < 1 then
- NoData
- else
- if ReadOnly then
- CannotChange
- else
- begin
- Stream.Seek(0, 0);
- Cache.Remove(Index.Strings[FItemIndex]);
- ThisKey := GetKey(FItemIndex);
- TmpStream := TMemoryStream.Create;
- if Compression then
- SquashStream(Stream, TmpStream)
- else
- TmpStream.LoadFromStream(Stream);
- TmpStream.Seek(0, 0);
- if TmpStream.size <= TIndex(Index.Objects[FItemIndex]).Len then
- begin {just write on top of the old}
- with TIndex(Index.Objects[FItemIndex]) do
- begin
- QDBFile.Seek(Pos, 0);
- Len := TmpStream.size;
- if Compression then
- Ext := Ext + [IsCompressed]
- else
- Ext := Ext - [IsCompressed];
- end;
- end
- else
- begin
- TIndex(Index.Objects[FItemIndex]).Free;
- Index.Delete(FItemIndex);
- try
- IndRec := TIndex.Create;
- except
- IndexError(SIndexAdd);
- end;
- IndRec.Pos := QDBFile.Seek(0, 2);
- if IndRec.Pos < 0 then
- FileError(-1, SDataAdd);
- IndRec.Len := TmpStream.size;
- if Compression then
- IndRec.Ext := IndRec.Ext + [IsCompressed]
- else
- IndRec.Ext := IndRec.Ext - [IsCompressed];
- try
- FItemIndex := Index.AddObject(ThisKey, IndRec);
- except
- IndRec.Free;
- raise
- end;
- end;
- QDBFile.CopyFrom(TmpStream, TmpStream.size);
- TmpStream.Free;
- Cache.Store(Stream, Index.Strings[FItemIndex]);
- FCount := Index.Count;
- IsDirty := true;
- Stream.Seek(0, 0);
- Changed;
- end;
- end;
-
- procedure TQDB.Changed;
- begin
- if Assigned(FOnChanged) then
- FOnChanged(Self);
- end;
-
- procedure TQDB.ChangeItem(ItemPtr: pointer; ItemLen: TDataIndex);
- { Change the contents of the current item }
- { If you can, use the Change method instead }
- var
- TmpStream: TMemoryStream;
- begin
- TmpStream := TMemoryStream.Create;
- try
- TmpStream.Write(ItemPtr^, ItemLen);
- Change(TmpStream);
- finally
- TmpStream.Free;
- end;
- end;
-
- (*// Alex
- procedure TQDB.ChangeKey(key: TKey);
- var
- IndRec: TIndex;
- begin
- IndRec := TIndex.Create;
- IndRec.pos := TIndex(Index.Objects[FItemIndex]).pos;
- IndRec.len := TIndex(Index.Objects[FItemIndex]).len;
- IndRec.ext := TIndex(Index.Objects[FItemIndex]).ext;
-
- Index.beginupdate;
- TIndex(Index.Objects[FItemIndex]).Free;
- Index.Delete(FItemIndex);
- FItemindex := Index.addobject(key, IndRec);
- Index.endupdate;
- end;
- *)
- procedure TQDB.ChangeStreamItem(Stream: TStream);
- { provided for backwards compatibility -- use Change instead }
- begin
- Change(Stream);
- end;
-
- function TQDB.CloseMatch(Partialkey: TKey): boolean;
- { looking for a near match -- stops where a full match would be }
- var
- n: TItemIndex;
- IsFound: boolean;
- begin
- Result := Index.Find(Partialkey, n);
- IsFound := Result;
- if (not Result) and (n >= 0) and (n < Index.Count) then
- begin
- if KeyCaseSensitive then
- IsFound := (Copy(Index[n], 1, Length(Partialkey)) = Partialkey)
- else
- IsFound := (LowerCase(Copy(Index[n], 1, Length(Partialkey))) = LowerCase(Partialkey));
- end;
- if IsFound then
- begin
- ItemIndex := n;
- Found;
- end;
- end;
-
- procedure TQDB.CloseQDB;
- { close up a QDB file }
- begin
- SaveIndex;
- FItemIndex := 0;
- FKey := '';
- FCount := 0;
- IsDirty := false;
- SetReady(false);
- Password := '';
- end;
-
- procedure TQDB.Compress;
- { compresses every item in the file -- not quick! }
- var
- m: TMemoryStream;
- n: TItemIndex;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly then
- begin
- CannotChange;
- exit;
- end;
- Compression := true;
- BeginUpdate;
- MonitorSetup(Index.Count, prCompress);
- for n := 1 to Index.Count do
- begin
- MonitorUpdate(n);
- if not ItemIsCompressed(n - 1) then
- begin
- m := TMemoryStream.Create;
- try
- FItemIndex := n - 1;
- Get(m);
- Change(m);
- finally
- m.Free;
- end;
- end;
- end;
- MonitorZero;
- Pack;
- EndUpdate;
- end;
-
- procedure TQDB.CreateQDB;
- { makes a new empty QDB file on disk ... }
- const
- Sig1: array[0..3] of char = ('Q', 'D', 'B', #0);
- Sig2: array[0..3] of char = ('Q', 'I', 'X', #0);
- var
- f: TFileHandle;
- sz1,
- sz2: longint;
- zero: Byte;
- bigzero: longint;
- begin
- zero := 0; { to null-terminate the index block }
- bigzero := 0; { number of items in the index = 0 }
- f := FileCreate(FileName);
- if f < 0 then
- FileError(f, '');
- try { protect file f }
- FileWrite(f, Sig1, SizeOf(Sig1));
- sz1 := 0;
- FileWrite(f, sz1, SizeOf(sz1));
- FileWrite(f, Sig2, SizeOf(Sig2));
- sz2 := SizeOf(bigzero) + SizeOf(zero);
- FileWrite(f, sz2, SizeOf(sz2));
- FileWrite(f, bigzero, SizeOf(bigzero));
- FileWrite(f, zero, SizeOf(zero));
- finally
- FileClose(f);
- end;
- end;
-
- procedure TQDB.DoDelete;
- { delete an item from the index -- need to Pack to get it }
- { out of the item file }
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly then
- CannotChange
- else
- if Index.Count < 1 then
- NoData
- else
- begin
- if not AutoEdit then
- FEditing := false;
- FInserting := false;
- Cache.Remove(Index.Strings[FItemIndex]);
- TIndex(Index.Objects[FItemIndex]).Free;
- Index.Delete(FItemIndex);
- if FItemIndex > 0 then
- ItemIndex := FItemIndex - 1
- else
- FKey := '';
- FCount := Index.Count;
- IsDirty := true;
- Deleted;
- UpdateNavigator;
- end;
- end;
-
- procedure TQDB.Delete;
- begin
- if Assigned(FBeforeDelete) then
- FBeforeDelete(Self);
- DoDelete;
- if Assigned(FAfterDelete) then
- FAfterDelete(Self);
- end;
-
- procedure TQDB.Deleted;
- begin
- if Assigned(FOnDeleted) then
- FOnDeleted(Self);
- end;
-
- procedure TQDB.DeleteItem;
- { provided for backward compatibility -- use Delete }
- begin
- Delete;
- end;
-
- procedure TQDB.DemandPassword;
- var
- NewPassword: TPassword;
- begin
- NewPassword := '';
- if Assigned(FOnDemandPassWord) then
- FOnDemandPassWord(Self, NewPassword);
- Password := NewPassword;
- end;
-
- procedure TQDB.DoEdit;
- begin
- FEditing := true;
- UpdateNavigator;
- end;
-
- procedure TQDB.Edit;
- begin
- if Assigned(FBeforeEdit) then
- FBeforeEdit(Self);
- DoEdit;
- if Assigned(FAfterEdit) then
- FAfterEdit(Self);
- end;
-
- procedure TQDB.EndUpdate;
- begin
- dec(FUpdating);
- if (FUpdating = 0) then
- Navigate;
- end;
-
- function TQDB.ExactMatch(Key: TKey): boolean;
- { simple stringlist find }
- var
- n: TItemIndex;
- begin
- Result := Index.Find(Key, n);
- if Result then
- begin
- ItemIndex := n;
- Found;
- end;
- end;
-
- procedure TQDB.Expand;
- { decompresses every item in the file -- not quick! }
- var
- m: TMemoryStream;
- n: TItemIndex;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly then
- begin
- CannotChange;
- exit;
- end;
- Compression := false;
- BeginUpdate;
- MonitorSetup(Index.Count, prCompress);
- for n := 1 to Index.Count do
- begin
- MonitorUpdate(n);
- if ItemIsCompressed(n - 1) then
- begin
- m := TMemoryStream.Create;
- try
- FItemIndex := n - 1;
- Get(m);
- Change(m);
- finally
- m.Free;
- end;
- end;
- end;
- MonitorZero;
- Pack;
- EndUpdate;
- end;
-
- procedure TQDB.FileAssigned;
- begin
- if Assigned(FOnFileAssigned) then
- FOnFileAssigned(Self);
- end;
-
- procedure TQDB.FileError(ErrCode: integer; SDefault: string);
- { report errors concerning file resources }
- var
- SErr: string;
- begin
- case ErrCode of
- - 1:
- SErr := SDefault;
- - 2:
- SErr := SMissing;
- - 3:
- SErr := SDoorOpen;
- - 4:
- SErr := STooMany;
- - 5:
- SErr := SShareError;
- - 101:
- SErr := SDiskFull;
- else
- raise EQDBFileError.CreateFmt(SUnknownError, [IntToStr(ErrCode)]);
- end;
- raise EQDBFileError.CreateFmt(SErr, [FFileName]);
- end;
-
- function TQDB.FileToRecover: string;
- { returns the name of an orphaned working file }
- var
- TmpPath: pchar;
- PlaceToLook: string;
- f: TSearchRec;
- OldQDBFileName: string;
- Handle: integer;
- begin
- Result := '';
- GetMem(TmpPath, MAX_PATH);
- try
- FillChar(TmpPath^, MAX_PATH, #0);
- {$IFDEF WIN32}
- GetTempPath(MAX_PATH, TmpPath);
- PlaceToLook := TmpPath;
- {$ELSE}
- GetTempFileName(#0, 'RRM', 0, TmpPath);
- PlaceToLook := ExtractFilePath(StrPas(TmpPath));
- {$ENDIF}
- finally
- FreeMem(TmpPath, MAX_PATH);
- end;
- if FindFirst(PlaceToLook + 'QDB*.tmp', 0, f) = 0 then
- begin
- OldQDBFileName := PlaceToLook + f.Name;
- SysUtils.FindClose(f);
- { check if in use at the moment or if genuine orphan ... }
- Handle := FileOpen(OldQDBFileName, fmShareExclusive);
- if Handle < 0 then
- exit;
- FileClose(Handle);
- Result := OldQDBFileName;
- end;
- end;
-
- procedure TQDB.FirstItem;
- var
- This: TItemIndex;
- begin
- FBoF := true;
- FEoF := false;
- if Filtered then
- begin
- { the filter slows things down ... }
- This := 0;
- while (This < Index.Count) and not Matches(GetKey(This), FFilter) do
- inc(This);
- ItemIndex := This;
- end
- else
- ItemIndex := 0;
- { if there is one, tell the navigator we've moved }
- UpdateNavigator;
- end;
-
- procedure TQDB.ForceOverwrite(Value: boolean);
- begin
- FForceOverwrite := Value;
- end;
-
-
- procedure TQDB.Found;
- begin
- if Assigned(FOnFound) then
- FOnFound(Self);
- end;
-
- procedure TQDB.Get(Stream: TStream);
- { retrieve an item from the file as a stream }
- var
- CacheN: longint;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if Index.Count > 0 then
- begin
- try
- Stream.Seek(0, 0);
- { first check if in cache }
- if Cache.Find(Index.Strings[FItemIndex], CacheN) then
- begin
- Cache.Fetch(Stream, CacheN);
- end
- else
- begin
- QDBFile.Seek(TIndex(Index.Objects[FItemIndex]).Pos, 0);
- Stream.Seek(0, 0);
- if ItemIsCompressed(FItemIndex) then
- UnSquashStream(QDBFile, Stream, TIndex(Index.Objects[FItemIndex]).Len)
- else
- Stream.CopyFrom(QDBFile, TIndex(Index.Objects[FItemIndex]).Len);
- Stream.Seek(0, 0);
- Cache.Store(Stream, Index.Strings[FItemIndex]);
- end;
- Stream.Seek(0, 0);
- except
- IndexError(SCorrupt);
- end;
- end
- else
- NoData;
- end;
-
- function TQDB.GetCacheFrequency: integer;
- { gets the percentage of accesses that hit the cache }
- begin
- Result := Cache.GetFrequency;
- end;
-
- function TQDB.GetCacheSize: longint;
- begin
- Result := Cache.FMaximumSize;
- end;
-
- function TQDB.GetFileName: TQDBFileName;
- begin
- Result := FFileName;
- end;
-
- function TQDB.GetFilteredCount: TItemIndex;
- { how many keys match the current filter? }
- var
- This: TItemIndex;
- i: TItemIndex;
- begin
- if Filtered then
- begin
- { not quick ... }
- i := 0;
- for This := 1 to Index.Count do
- if Matches(GetKey(This - 1), FFilter) then
- inc(i);
- Result := i;
- end
- else
- Result := FCount;
- end;
-
- procedure TQDB.GetItem(ItemPtr: pointer);
- { retrieve the current item -- try to use Get instead }
- var
- TmpStream: TMemoryStream;
- begin
- TmpStream := TMemoryStream.Create;
- try
- GetStreamItem(TmpStream);
- TmpStream.Read(ItemPtr^, TmpStream.size);
- finally
- TmpStream.Free;
- end;
- end;
-
- function TQDB.GetItemSize(Value: TItemIndex): TDataIndex;
- { returns the number of bytes an item occupies on the disk }
- begin
- Result := TIndex(Index.Objects[Value]).Len;
- end;
-
- function TQDB.GetKey(Value: TItemIndex): TKey;
- { fetches the key for the given item }
- begin
- if Index.Count < 1 then
- begin
- Result := '';
- NoData;
- end
- else
- Result := Index.Strings[Value];
- end;
-
- function TQDB.GetKeyCase: boolean;
- { is the index case-sensitive ? }
- begin
- Result := Index.CaseSensitive;
- end;
-
- function TQDB.GetStr(n: TItemIndex): string;
- { gets item n as a string }
- begin
- Result := '';
- ItemIndex := n;
- Result := GetThisStr;
- end;
-
- function TQDB.GetStrByKey(Key: TKey): string;
- { if key exists gets item as string }
- begin
- if ExactMatch(Key) then
- Result := GetThisStr
- else
- raise EQDBBadKey.CreateFmt(SBadKey, [Key]);
- end;
-
- procedure TQDB.GetStreamItem(Stream: TStream);
- { provided for compatibility -- use Get }
- begin
- Get(Stream);
- end;
-
- function TQDB.GetThisItemSize: TDataIndex;
- { returns the number of bytes the current item occupies }
- { in memory -- may have to uncompress an item to tell }
- var
- m: TMemoryStream;
- n: TDataIndex;
- begin
- if Index.Count < 1 then
- begin
- Result := 0;
- NoData;
- end
- else
- if ItemIsCompressed(FItemIndex) then
- begin
- { look in Cache }
- if Cache.Find(Key, n) then
- begin
- Result := TCache(Cache.Objects[n]).Stream.size;
- end
- else
- begin
- m := TMemoryStream.Create;
- try
- Get(m);
- Result := m.size;
- finally
- m.Free;
- end;
- end;
- end
- else
- Result := GetItemSize(FItemIndex);
- end;
-
- function TQDB.GetThisStr: string;
- { gets the current item as a string -- truncates D1 }
- { strings if needed }
- var
- m: TMemoryStream;
- size: longint;
- begin
- m := TMemoryStream.Create;
- try
- Get(m);
- size := m.size;
- {$IFNDEF WIN32}
- if size > 255 then
- size := 255;
- Result[0] := chr(size);
- {$ELSE}
- SetLength(Result, size);
- {$ENDIF}
- m.Read(Result[1], size);
- finally
- m.Free;
- end;
- end;
-
- function TQDB.GrepMatches(Key: TKey; Pattern: TKey): boolean;
- begin
- Result := GrepMatch(Key, Pattern, KeyCaseSensitive, FMatchWholeWord);
- end;
-
- procedure TQDB.IndexError(ErrMsg: string);
- { reports errors to do with the Index }
- begin
- raise EQDBIndexError.CreateFmt(ErrMsg, [FFileName]);
- end;
-
- procedure TQDB.DoInsert;
- begin
- FInserting := true;
- UpdateNavigator;
- end;
-
- procedure TQDB.Insert;
- begin
- if Assigned(FBeforeInsert) then
- FBeforeInsert(Self);
- DoInsert;
- if Assigned(FAfterInsert) then
- FAfterInsert(Self);
- end;
-
- function TQDB.ItemIsCompressed(Value: TItemIndex): boolean;
- { is the item compressed? }
- begin
- Result := IsCompressed in TIndex(Index.Objects[Value]).Ext;
- end;
-
- function TQDB.KeyExists(Key: TKey): boolean;
- var
- n: TItemIndex;
- begin
- Result := Index.Find(Key, n);
- end;
-
- procedure TQDB.Kill;
- { Erases a QDB file after asking confirmation }
- var
- OK: boolean;
- FileToDel: string;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- OK := false;
- AboutToKill(OK);
- if not OK then
- exit;
- if ReadOnly then
- begin
- CannotChange;
- exit;
- end;
- if FFileName <> '' then
- begin
- FileToDel := FileName;
- FileName := '';
- SysUtils.DeleteFile(FileToDel);
- Killed;
- end;
- end;
-
- procedure TQDB.Killed;
- begin
- if Assigned(FOnKilled) then
- FOnKilled(Self);
- end;
-
- procedure TQDB.LastItem;
- var
- This: TItemIndex;
- begin
- FBoF := false;
- FEoF := true;
- if Filtered then
- begin
- This := Index.Count - 1;
- while (This >= 0) and not Matches(GetKey(This), FFilter) do
- dec(This);
- ItemIndex := This;
- end
- else
- ItemIndex := Index.Count - 1;
- { if there is one, tell the navigator we've moved }
- UpdateNavigator;
- end;
-
- function TQDB.Live: boolean;
- begin
- Result := Restructuring or not (csDesigning in ComponentState);
- end;
-
- procedure TQDB.LoadIndex;
- { Loading and saving the index are the most complex tasks in the unit }
- { Some words about the working file formats is in order: }
- { The item file (QDBFile) consists of contiguous, variable-length, }
- { blocks of data. Where one ends and the next begins is known only }
- { to the index file (QIXFile). LoadIndex reads this data into }
- { its Index list. The index file format is as follows: }
- { }
- { 4 bytes -- longint(n) = number of items in the file }
- { n variable length blocks of the following structure }
- { null terminated string data = the key to an item }
- { 4 bytes -- longint(n1) = the place in the item file }
- { 4 bytes -- longint(n2) = the size of the item }
- { 4 bytes -- longint(n3) = binary attribute flags }
- { }
- var
- Key: TKey;
- Rec: TIndex;
- NumItems: TItemIndex;
- n: TItemIndex;
- begin
- try { except any error }
- Split;
- QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or
- fmShareExclusive);
- QIXFile := TBFStream.Create(QIXFileName, fmOpenReadWrite or
- fmShareExclusive, -1);
- try { protect file QIXFile }
- QIXFile.GetLongint(NumItems);
- MonitorSetup(NumItems, prStart);
- Index.SetCapacity(NumItems);
- Index.Sorted := false; { quicker to sort later than add to a sorted list }
- for n := 1 to NumItems do
- begin
- QIXFile.GetKey(Key);
- try
- Rec := TIndex.Create;
- except
- IndexError(SIndexAdd);
- end;
- QIXFile.GetLongint(Rec.Pos);
- QIXFile.GetLongint(Rec.Len);
- QIXFile.GetLongint(longint(Rec.Ext));
- try
- if IsAdminItem in Rec.Ext then
- FAdminIndex := Admin.AddObject(Key, Rec)
- else
- begin
- FItemIndex := Index.AddObject(Key, Rec);
- end;
- except
- Rec.Free;
- Index.EmptyAndClear;
- Index.Sorted := true;
- Admin.EmptyAndClear;
- IndexError(SIndexAdd);
- end;
- MonitorUpdate(n);
- end;
- FItemIndex := 0;
- FAdminIndex := 0;
- FCount := Index.Count;
- IsDirty := false;
- Index.Reverse;
- MonitorZero;
- if FCount > 0 then
- begin
- FKey := GetKey(FItemIndex);
- end
- else
- begin
- FKey := '';
- end;
- finally
- QIXFile.Destroy;
- end;
- except
- on EOutOfMemory do
- IndexError(SCorrupt);
- on ERangeError do
- IndexError(SCorrupt);
- on EQDBListError do
- IndexError(SCorrupt);
- else
- raise;
- end;
- end;
-
- procedure TQDB.MonitorSetup(const Max: longint; const Kind: TProgressOrigin
- );
- { start monitoring the progress of a lengthy process }
- begin
- MonitorInterval := (Max div 10) + 1;
- MonitorKind := Kind;
- end;
-
- procedure TQDB.MonitorUpdate(const n: longint);
- { update the progress monitor }
- begin
- if n mod MonitorInterval = 0 then
- begin
- Application.ProcessMessages;
- SignalProgress((10 * n) div MonitorInterval, MonitorKind);
- end;
- end;
-
- procedure TQDB.MonitorZero;
- begin
- SignalProgress(0, MonitorKind);
- end;
-
- procedure TQDB.Navigate;
- { whenever the ItemIndex is changed }
- begin
- if Assigned(FOnNavigate) then
- FOnNavigate(Self);
- end;
-
- procedure TQDB.NextItem;
- var
- This: TItemIndex;
- begin
- FEoF := false;
- FBoF := false;
- This := FItemIndex;
- if Filtered then
- begin
- while (This + 1 < Index.Count) and not Matches(GetKey(This + 1), FFilter
- ) do
- begin
- inc(This);
- end;
- end;
- inc(This);
- if This >= Index.Count then
- begin
- FBoF := false;
- FEoF := true;
- end
- else
- begin
- ItemIndex := This;
- end;
- { if there is one tell the navigator we've moved }
- UpdateNavigator;
- end;
-
- procedure TQDB.NoData;
- { If a warning handler has not been assigned an exception is raised }
- { To silence exceptions assign at least an empty handler }
- begin
- if Assigned(FWarnNoData) then
- FWarnNoData(Self)
- else
- raise EQDBNoData.CreateFmt(SNoData, [FileName]);
- end;
-
- procedure TQDB.Notification(AComponent: TComponent; Operation: TOperation);
- { if the link to a navigator is broken we must respond }
- begin
- inherited Notification(AComponent, Operation);
- if (FQDBNavigator <> nil) and
- (AComponent = FQDBNavigator) and
- (Operation = opRemove) then
- FQDBNavigator := nil;
- end;
-
- procedure TQDB.OpenQDB;
- { open a QDB file with the current filename }
- begin
- try
- if not FileExists(FFileName) then
- CreateQDB;
- LoadIndex;
- SetReady(true);
- if AdminKeyExists('QDBCaseSensitive') then
- Index.CaseSensitive := AdminAsBoolean['QDBCaseSensitive'];
- except
- raise
- end;
- end;
-
- function TQDB.OrphanToRecover: boolean;
- { is there at least one orphaned file... }
- begin
- Result := (FileToRecover <> '');
- end;
-
- procedure TQDB.OutOfBounds;
- { If a warning handler has not been assigned an exception is raised }
- { To silence exceptions assign at least an empty handler }
-
- begin
- if Assigned(FWarnOutOfBounds) then
- FWarnOutOfBounds(Self)
- else
- raise EQDBOutOfBounds.CreateFmt(SOutOfBounds, [FFileName]);
- end;
-
- procedure TQDB.Pack;
- { Re-organizes the working item file into index order }
- { eliminating any unreferenced items. }
- var
- TmpFile: TFileStream;
- DatBuf: pchar;
- QLen: TDataIndex;
- QPos: TFilePos;
- TmpFileName: string;
- This, Init: TItemIndex;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly then
- begin
- CannotChange;
- exit;
- end;
- Init := FItemIndex;
- TmpFileName := TempFileName('QDB');
- if TmpFileName = '' then
- IndexError(STempFile);
- TmpFile := TFileStream.Create(TmpFileName, fmCreate);
- try { protect file tmpfile }
- MonitorSetup(Index.Count, prPack);
- for This := 1 to Admin.Count do
- begin
- QLen := TIndex(Admin.Objects[This - 1]).Len;
- QPos := TIndex(Admin.Objects[This - 1]).Pos;
- GetMem(DatBuf, QLen);
- try { protect memory DatBuf }
- try { catch file errors }
- TIndex(Admin.Objects[This - 1]).Pos := TmpFile.Seek(0, 2);
- QDBFile.Seek(QPos, 0);
- QDBFile.Read(DatBuf^, QLen);
- except
- IndexError(SCorrupt);
- end;
- TmpFile.Write(DatBuf^, QLen);
- finally
- FreeMem(DatBuf, QLen);
- end;
- end;
- for This := 1 to Index.Count do
- begin
- MonitorUpdate(This);
- QLen := TIndex(Index.Objects[This - 1]).Len;
- QPos := TIndex(Index.Objects[This - 1]).Pos;
- GetMem(DatBuf, QLen);
- try { protect memory DatBuf }
- try { catch file errors }
- TIndex(Index.Objects[This - 1]).Pos := TmpFile.Seek(0, 2);
- QDBFile.Seek(QPos, 0);
- QDBFile.Read(DatBuf^, QLen);
- except
- IndexError(SCorrupt);
- end;
- TmpFile.Write(DatBuf^, QLen);
- finally
- FreeMem(DatBuf, QLen);
- end;
- end;
- finally
- TmpFile.Free;
- end;
- QDBFile.Free;
- RenameOrMoveFile(TmpFileName, QDBFileName);
- QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or fmShareExclusive);
- if Init > 0 then
- ItemIndex := Init;
- MonitorZero;
- end;
-
- function TQDB.PartialMatch(StartOfKey: TKey): boolean;
- { finds the next key which begins with the right chars -- if }
- { you want to include the first item call PartialMatchInit first }
- var
- n: TItemIndex;
- k: TKey;
- T: TItemIndex;
- begin
- Result := false;
- if not KeyCaseSensitive then
- StartOfKey := LowerCase(StartOfKey);
- begin
- T := FItemIndex;
- if KeyCaseSensitive then
- begin
- for n := T + 2 to Count do
- begin
- k := Index.Strings[n - 1];
- if Copy(k, 1, Length(StartOfKey)) = StartOfKey then
- begin
- Result := true;
- Break;
- end;
- end;
- end
- else
- begin
- for n := T + 2 to Count do
- begin
- k := Index.Strings[n - 1];
- if LowerCase(Copy(k, 1, Length(StartOfKey))) = StartOfKey then
- begin
- Result := true;
- Break;
- end;
- end;
- end;
- if Result and (T <> n - 1) then
- begin
- ItemIndex := n - 1;
- Found;
- end;
- end;
- end;
-
- procedure TQDB.PartialMatchInit;
- begin
- FItemIndex := -1;
- end;
-
- function TQDB.PatternMatch(Pattern: TKey): boolean;
- { brute force search for a pattern -- not quick! }
- var
- n: TItemIndex;
- k: TKey;
- T: TItemIndex;
- begin
- T := FItemIndex;
- Result := false;
- for n := T + 1 to Count do
- begin
- k := GetKey(n - 1);
- if Matches(k, Pattern) then
- begin
- Result := true;
- Break;
- end;
- end;
- if Result and (T <> n - 1) then
- begin
- ItemIndex := n - 1;
- Found;
- end;
- end;
-
- procedure TQDB.PatternMatchInit;
- begin
- FItemIndex := -1;
- end;
-
- procedure TQDB.DoPost;
- begin
- if not AutoEdit then
- FEditing := false;
- FInserting := false;
- UpdateNavigator;
- end;
-
- procedure TQDB.Post;
- begin
- if Assigned(FBeforePost) then
- FBeforePost(Self);
- DoPost;
- if Assigned(FAfterPost) then
- FAfterPost(Self);
- end;
-
- procedure TQDB.PrepareToAdd(numberofitems: longint);
- { Usually the memory allocated for the index grows whenever needed, }
- { which can be time-consuming with all the moving of memory blocks, }
- { etc. Instead PrepareToAdd allocates all the memory required in }
- { go which is much quicker and reduces memory fragmentation. }
- begin
- Index.SetCapacity(Index.Count + numberofitems);
- end;
-
- procedure TQDB.PrevItem;
- var
- This: TItemIndex;
- begin
- FBoF := false;
- FEoF := false;
- This := FItemIndex;
- if Filtered then
- begin
- while (This - 1 >= 0) and not Matches(GetKey(This - 1), FFilter) do
- begin
- dec(This);
- end;
- end;
- dec(This);
- if This < 0 then
- begin
- FBoF := true;
- FEoF := false;
- end
- else
- begin
- ItemIndex := This;
- end;
- { if there is one, tell the navigator we've moved }
- UpdateNavigator;
- end;
-
- procedure TQDB.Recover(NewFileName: string);
- { Checks to see if there are temp working files still around }
- { that are not in use. }
- var
- Remnant: string;
- begin
- if FFileName <> '' then
- exit; { only use if nothing open }
- Remnant := FileToRecover;
- if Remnant = '' then
- exit; { and there is an orphan }
- { prepare to splice the working files together }
- QDBFileName := Remnant;
- QIXFileName := ExtractFilePath(Remnant) + 'QIX' + Copy(ExtractFileName(
- Remnant), 4, MAX_PATH);
- FFileAge := 0;
- FFileName := NewFileName;
- Splice;
- FFileName := '';
- SysUtils.DeleteFile(QDBFileName);
- SysUtils.DeleteFile(QIXFileName);
- { then open it up again }
- FileName := NewFileName;
- end;
-
- procedure TQDB.Refresh;
- begin
- end;
-
- procedure TQDB.Save;
- { Save commits the in-memory index to the working file }
- var
- T: TIndex;
- i, n: longint;
- NumItems: longint;
- TmpFile: TTempBFStream;
- begin
- if not FReady then
- raise EQDBNoFile.Create(SNoFile);
- if ReadOnly or not IsDirty then
- exit;
- MonitorSetup(Index.Count, prSave);
- TmpFile := TTempBFStream.Create(QIXFileName);
- try { protect file tmpfile }
- NumItems := Index.Count + Admin.Count;
- TmpFile.PutLongint(NumItems);
- i := Admin.Count;
- while i > 0 do
- begin
- TmpFile.PutKey(Admin.Strings[i - 1]);
- T := TIndex(Admin.Objects[i - 1]);
- TmpFile.PutLongint(T.Pos);
- TmpFile.PutLongint(T.Len);
- TmpFile.PutLongint(longint(T.Ext));
- dec(i);
- end;
- i := Index.Count;
- n := 0;
- while i > 0 do
- begin
- MonitorUpdate(n);
- inc(n);
- TmpFile.PutKey(Index.Strings[i - 1]);
- T := TIndex(Index.Objects[i - 1]);
- TmpFile.PutLongint(T.Pos);
- TmpFile.PutLongint(T.Len);
- TmpFile.PutLongint(longint(T.Ext));
- dec(i);
- end;
- finally
- TmpFile.Destroy;
- end;
- QDBFile.Free;
- IsDirty := false;
- QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or fmShareExclusive
- );
- MonitorZero;
- end;
-
- procedure TQDB.SaveAs(NewName: string);
- { SaveAs first commits the in-memory index to the working file }
- { before writing a copy of index and items to a new QDB file }
- begin
- Save;
- QDBFile.Free;
- FFileName := ExpandFileName(NewName);
- if ExtractFileExt(FFileName) = '' then
- FFileName := ChangeFileExt(FFileName, '.QDB');
- FFileAge := 0;
- Splice;
- FFileAge := FileAge(FFileName);
- QDBFile := TFileStream.Create(QDBFileName, fmOpenReadWrite or fmShareExclusive
- );
- IsDirty := false;
- end;
-
- procedure TQDB.SaveIndex;
- { Saving the index is just as messy as loading it ... }
- var
- T: TIndex;
- n: TItemIndex;
- NumItems: TItemIndex;
- TmpFile: TTempBFStream;
- begin
- MonitorSetup(Index.Count, prFinish);
- if ReadOnly or not SaveOnClose then
- begin
- while Admin.Count > 0 do
- begin
- TIndex(Admin.Objects[Admin.Count - 1]).Free;
- Admin.Delete(Admin.Count - 1);
- end;
- n := 0;
- while Index.Count > 0 do
- begin
- MonitorUpdate(n);
- inc(n);
- TIndex(Index.Objects[Index.Count - 1]).Free;
- Index.Delete(Index.Count - 1);
- end;
- end
- else
- begin
- TmpFile := TTempBFStream.Create(QIXFileName);
- try { protect file tmpfile }
- NumItems := Index.Count + Admin.Count;
- TmpFile.PutLongint(NumItems);
- while Admin.Count > 0 do
- begin
- TmpFile.PutKey(Admin.Strings[Admin.Count - 1]);
- T := TIndex(Admin.Objects[Admin.Count - 1]);
- TmpFile.PutLongint(T.Pos);
- TmpFile.PutLongint(T.Len);
- TmpFile.PutLongint(longint(T.Ext));
- T.Free;
- Admin.Delete(Admin.Count - 1);
- end;
- n := 0;
- while Index.Count > 0 do
- begin
- MonitorUpdate(n);
- inc(n);
- TmpFile.PutKey(Index.Strings[Index.Count - 1]);
- T := TIndex(Index.Objects[Index.Count - 1]);
- TmpFile.PutLongint(T.Pos);
- TmpFile.PutLongint(T.Len);
- TmpFile.PutLongint(longint(T.Ext));
- T.Free;
- Index.Delete(Index.Count - 1);
- end;
- finally
- TmpFile.Destroy;
- end;
- end;
- Cache.Flush;
- QDBFile.Free;
- IsDirty := false;
- Splice;
- SysUtils.DeleteFile(QDBFileName);
- SysUtils.DeleteFile(QIXFileName);
- MonitorZero;
- end;
-
- procedure TQDB.SetCacheFrequency(Value: integer);
- { resets cache-hit counting -- notice that whatever the }
- { value the result is the same. }
- begin
- Cache.SetFrequency(Value);
- end;
-
- procedure TQDB.SetCacheSize(Value: longint);
- { sets the upper limit on the cache's size -- flushing }
- { the cache in the process }
- begin
- Cache.SetSize(Value);
- end;
-
- procedure TQDB.SetDummyAuthor(Value: string40);
- begin
- { does nothing but make a read-only property visible in Object Inspector }
- end;
-
- procedure TQDB.SetDummyVersion(Value: string05);
- begin
- { does nothing but make a read-only property visible in Object Inspector }
- end;
-
- procedure TQDB.SetFileName(Value: string);
- { setting the FileName property loads and unloads QDB files }
- begin
- try
- if FExpandedFileNames and (Value <> '') then
- Value := ExpandFileName(Value);
- if ExpandFileName(FFileName) <> ExpandFileName(Value) then
- begin
- if Live and (FFileName <> '') then
- CloseQDB;
- if Value = '' then
- FFileName := ''
- else
- begin
- FFileName := Value;
- if ExtractFileExt(FFileName) = '' then
- FFileName := ChangeFileExt(FFileName, '.QDB');
- end;
- if Live and (FFileName <> '') then
- OpenQDB;
- FileAssigned; { trigger event }
- end;
- except
- FFileName := ''; { if anything goes wrong ... }
- raise;
- end;
- end;
-
- procedure TQDB.SetFilter(Value: TKey);
- { sets a filter to restrict navigation }
- begin
- Filtered := (Value <> ''); { we use this flag a lot elsewhere }
- FFilter := Value;
- end;
-
- procedure TQDB.SetGrepMatch(Value: boolean);
- begin
- if Value then
- Matches := GrepMatches
- else
- Matches := SimpleMatches;
- FGrepMatch := Value;
- end;
-
- procedure TQDB.SetItemIndex(Value: TItemIndex);
- { does all the work of moving about the index }
- begin
- if Index.Count = 0 then
- NoData { trigger event if empty }
- else
- if (Value < 0) or (Value > Index.Count - 1) then
- OutOfBounds { trigger event if illegal move }
- else
- begin
- FItemIndex := Value; { new index position }
- FKey := GetKey(FItemIndex); { updated properties }
- if (FUpdating = 0) then
- Navigate; { trigger event when index pos changes }
- end
- end;
-
- procedure TQDB.SetKeyCase(Value: boolean);
- { sets the case-sensitivity of the index }
- begin
- { if value is false the index is checked and any duplicates purged }
- Index.CaseSensitive := Value;
- {the case-sensitivity is stored in the QDB file }
- AdminAsBoolean['QDBCaseSensitive'] := Value;
- end;
-
- procedure TQDB.SetLinkToNavigator(Value: TQDBNavigator);
- { note which navigator (if any) is using this QDB }
- begin
- FQDBNavigator := Value;
- UpdateNavigator;
- end;
-
- procedure TQDB.SetMatchChars(Front: char; back: char);
- { defines the wild card chars for the simple pattern matching }
- begin
- if Front <> #0 then
- FFrontWild := Front;
- if back <> #0 then
- FBackWild := back;
- end;
-
- procedure TQDB.SetReadOnly(Value: boolean);
- { sets the ReadOnly state of the QDB and notifies the navigator }
- begin
- if Value <> FReadOnly then
- begin
- FReadOnly := Value;
- UpdateNavigator;
- end;
- end;
-
- procedure TQDB.SetReady(Value: boolean);
- { sets the Ready state of the QDB and notifies the navigator }
- begin
- if Value <> FReady then
- begin
- FReady := Value;
- UpdateNavigator;
- end;
- end;
-
- procedure TQDB.SetStr(n: TItemIndex; const Value: string);
- { sets the item n as a string }
- begin
- ItemIndex := n;
- SetThisStr(Value);
- end;
-
- procedure TQDB.SetStrByKey(Key: TKey; const Value: string);
- { look up key and add or change item as string }
- var
- m: TMemoryStream;
- begin
- if ExactMatch(Key) then
- SetThisStr(Value)
- else
- begin
- m := TMemoryStream.Create;
- try
- m.Write(Value[1], Length(Value));
- m.Seek(0, 0);
- Add(m, Key);
- finally
- m.Free;
- end;
- end;
- end;
-
- procedure TQDB.SetThisStr(const Value: string);
- { sets -- adds or changes -- current item as string }
- var
- m: TMemoryStream;
- begin
- m := TMemoryStream.Create;
- try
- m.Write(Value[1], Length(Value));
- m.Seek(0, 0);
- Change(m);
- finally
- m.Free;
- end;
- end;
-
- procedure TQDB.SignalProgress(Percent: TPercentage; Kind: TProgressOrigin);
- begin
- if Assigned(FProgressUpdate) then
- FProgressUpdate(Self, Percent, Kind);
- end;
-
- function TQDB.SimpleMatches(Key: TKey; Pattern: TKey): boolean;
- { used by the filtering system -- if you wanted a more }
- { sophisticated match you would override this function. }
- var
- IsMatch: boolean;
- LeftPos,
- RightPos,
- PatternPosF,
- PatternPosL,
- LenP,
- LastPos: Byte;
- begin
- if not KeyCaseSensitive then
- begin
- Key := UpperCase(Key);
- Pattern := UpperCase(Pattern);
- end;
- LenP := Length(Pattern);
- LeftPos := Pos(FFrontWild, Pattern);
- if LeftPos = 1 then
- begin
- dec(LenP);
- Pattern := Copy(Pattern, 2, LenP);
- end;
- RightPos := Pos(FBackWild, Pattern);
- if (RightPos = LenP) and (LenP <> 0) then
- begin
- dec(LenP);
- Pattern := Copy(Pattern, 1, LenP);
- end
- else
- begin
- if LenP = 0 then { Clester Keaton }
- begin
- Result := true;
- exit;
- end;
- end;
- if LenP = 0 then
- begin
- PatternPosF := 1;
- PatternPosL := 1;
- end
- else
- begin
- PatternPosF := Pos(Pattern, Key);
- PatternPosL := PatternPosF;
- if PatternPosL <> 0 then
- begin
- LastPos := PatternPosL;
- while LastPos <> 0 do
- begin
- LastPos := Pos(Pattern, Copy(Key, PatternPosL + 1, Length(Key)));
- PatternPosL := PatternPosL + LastPos;
- end;
- end;
- end;
- IsMatch := PatternPosF <> 0;
- if IsMatch and (LeftPos = 0) and (PatternPosF <> 1) then
- begin
- IsMatch := false;
- end;
- if IsMatch and (RightPos = 0) and (PatternPosL <> (Length(Key) - LenP + 1
- )) then
- begin
- IsMatch := false;
- end;
- Result := IsMatch;
- end;
-
- procedure TQDB.Splice;
- { Splices the two working files back into a single QDB file, }
- { takingcare not to overwrite the original if it has been used }
- { by another program. }
- const
- Sig1: array[0..3] of char = ('Q', 'D', 'B', #0);
- Sig2: array[0..3] of char = ('Q', 'I', 'X', #0);
- Sig3: array[0..3] of char = ('Q', 'P', 'W', #0);
- var
- n: longint;
- TmpFileName: string;
- f, f1, f2: TFileHandle;
- Buffer: pointer;
- sz1, sz2: longint;
- BytesRead: TFilePos;
- BufLen: longint;
- CanOverWrite: boolean;
- PassHash: THash;
- WriteHash: THash;
- Encrypt: boolean;
- begin
- if ReadOnly then
- exit;
- { check if the file has been used since we opened it -- }
- { if FFileAge = 0 it means we are doing a SaveAs... }
- if (FFileAge <> 0) and (FileAge(FFileName) <> FFileAge) then
- begin
- { it has so we need to ask if we can save our stuff over it }
- CanOverWrite := FForceOverwrite; { be conservative }
- AboutToOverWrite(CanOverWrite);
- if not CanOverWrite then
- repeat
- { since we can't overwrite the original we have }
- { to look for a unique derivative of the filename }
- FFileName := ExtractFilePath(FFileName) + '1.' + ExtractFileName(
- FFileName);
- until not FileExists(FileName);
- { then we can go ahead as normal }
- end;
- Encrypt := false;
- TmpFileName := TempFileName('QDD');
- f := FileCreate(TmpFileName);
- if f < 0 then
- FileError(f, '');
- try { protect file f }
- FileWrite(f, Sig1, SizeOf(Sig1));
- if Password <> '' then
- begin
- FileWrite(f, Sig3, SizeOf(Sig3));
- WriteHash := Hash(Password);
- PassHash := Hash(WriteHash);
- FileWrite(f, PassHash, SizeOf(THash));
- Encrypt := true;
- end;
- f1 := FileOpen(QDBFileName, fmOpenRead or fmShareExclusive);
- if f1 < 0 then
- FileError(f1, '');
- try { protect file f1 }
- sz1 := FileSeek(f1, 0, 2);
- FileWrite(f, sz1, SizeOf(sz1));
- FileSeek(f1, 0, 0);
- BufLen := GetBuffer(Buffer, sz1);
- try { protect memory buffer }
- for n := 1 to (sz1 div BufLen) do
- begin
- BytesRead := FileRead(f1, Buffer^, BufLen);
- if Encrypt then
- Shroud(Buffer^, BytesRead, WriteHash);
- FileWrite(f, Buffer^, BytesRead);
- end;
- BytesRead := FileRead(f1, Buffer^, sz1 mod BufLen);
- if Encrypt then
- Shroud(Buffer^, BytesRead, WriteHash);
- FileWrite(f, Buffer^, BytesRead);
- finally
- FreeMem(Buffer, BufLen);
- end;
- finally
- FileClose(f1);
- end;
- FileWrite(f, Sig2, SizeOf(Sig2));
- f2 := FileOpen(QIXFileName, fmOpenRead or fmShareExclusive);
- if f2 < 0 then
- FileError(f2, '');
- try { protect file f2 }
- sz2 := FileSeek(f2, 0, 2);
- FileWrite(f, sz2, SizeOf(sz2));
- FileSeek(f2, 0, 0);
- BufLen := GetBuffer(Buffer, sz2);
- try { protect memory buffer }
- for n := 1 to (sz2 div BufLen) do
- begin
- BytesRead := FileRead(f2, Buffer^, BufLen);
- if Encrypt then
- Shroud(Buffer^, BytesRead, WriteHash);
- FileWrite(f, Buffer^, BytesRead);
- end;
- BytesRead := FileRead(f2, Buffer^, sz2 mod BufLen);
- if Encrypt then
- Shroud(Buffer^, BytesRead, WriteHash);
- FileWrite(f, Buffer^, BytesRead);
- finally
- FreeMem(Buffer, BufLen);
- end;
- finally
- FileClose(f2);
- end;
- finally
- FileClose(f);
- RenameOrMoveFile(TmpFileName, FFileName);
- FFileAge := FileAge(FFileName);
- end;
- end;
-
- procedure TQDB.Split;
- { This seems like a good place to document the QDB file format ... }
- { }
- { 4 bytes -- 'Q','D','B',#0 }
- {(24 bytes -- optional password block present if file is encrypted }
- { 'Q','P','W',#0 indicates encryption }
- { 20 bytes of encrypted password ) }
- { 4 bytes -- longint(n1) = size of data block }
- { n1 bytes of data }
- { 4 bytes -- 'Q','I','X',#0 }
- { 4 bytes -- longint(n2) = size of index block }
-
- { Split takes a QDB file and, testing it for integrity, }
- { splits it into two working files, one the item data, }
- { the other the index data. These are the files that the }
- { QDB component uses internally. The original QDB file }
- { doesn't get reconstituted until the Splice method. }
- var
- n: longint;
- f, f1, f2: TFileHandle;
- Buffer: pointer;
- Sig: array[0..3] of char;
- sz1, sz2: longint;
- BytesRead: TFilePos;
- BufLen: longint;
- PassHash: THash;
- ReadHash: THash;
- ReadHash2: THash;
- Decrypt: boolean;
- begin
- Decrypt := false;
- { we get the age of the file when it was opened }
- FFileAge := FileAge(FFileName);
- if (faReadOnly and FileGetAttr(FFileName)) <> 0 then
- ReadOnly := true;
- f := FileOpen(FFileName, fmOpenRead);
- if f < 0 then
- FileError(f, '');
- try { protect file f }
- FileRead(f, Sig, SizeOf(Sig));
- if StrPas(Sig) <> 'QDB' then
- FileError(-1, SCorrupt);
- BytesRead := FileRead(f, Sig, SizeOf(Sig));
- if StrPas(Sig) = 'QPW' then
- begin
- { process password }
- FileRead(f, PassHash, SizeOf(THash));
- if Password = '' then
- DemandPassword;
- ReadHash := Hash(Password);
- ReadHash2 := Hash(ReadHash);
- if Hash(ReadHash) = PassHash then
- begin
- { we have a match }
- Decrypt := true;
- end
- else
- begin
- { file demands a password and we can't deliver }
- raise EQDBInvalidPW.Create(SBadPassword);
- end;
- end
- else
- begin
- { rewind and continue }
- FileSeek(f, -BytesRead, 1)
- end;
- FileRead(f, sz1, SizeOf(sz1));
- BufLen := GetBuffer(Buffer, sz1);
- try { protect memory buffer }
- QDBFileName := TempFileName('QDB');
- f1 := FileCreate(QDBFileName);
- if f1 < 0 then
- FileError(f1, '');
- try { protect file f1 }
- for n := 1 to (sz1 div BufLen) do
- begin
- BytesRead := FileRead(f, Buffer^, BufLen);
- if Decrypt then
- UnShroud(Buffer^, BytesRead, ReadHash);
- FileWrite(f1, Buffer^, BytesRead);
- end;
- BytesRead := FileRead(f, Buffer^, sz1 mod BufLen);
- if Decrypt then
- UnShroud(Buffer^, BytesRead, ReadHash);
- FileWrite(f1, Buffer^, BytesRead);
- finally
- FileClose(f1);
- end;
- finally
- FreeMem(Buffer, BufLen);
- end;
- FileRead(f, Sig, SizeOf(Sig));
- if StrPas(Sig) <> 'QIX' then
- FileError(-1, SCorrupt);
- FileRead(f, sz2, SizeOf(sz2));
- BufLen := GetBuffer(Buffer, sz2);
- try { protect memory buffer }
- { use same 'random' name as for QDBFileName }
- QIXFileName := ExtractFilePath(QDBFileName) + 'QIX' + Copy(
- ExtractFileName(QDBFileName), 4, MAX_PATH);
- f2 := FileCreate(QIXFileName);
- if f2 < 0 then
- FileError(f2, '');
- try { protect file f2 }
- for n := 1 to (sz2 div BufLen) do
- begin
- BytesRead := FileRead(f, Buffer^, BufLen);
- if Decrypt then
- UnShroud(Buffer^, BytesRead, ReadHash);
- FileWrite(f2, Buffer^, BytesRead);
- end;
- BytesRead := FileRead(f, Buffer^, sz2 mod BufLen);
- if Decrypt then
- UnShroud(Buffer^, BytesRead, ReadHash);
- FileWrite(f2, Buffer^, BytesRead);
- finally
- FileClose(f2);
- end;
- finally
- FreeMem(Buffer, BufLen);
- end;
- finally
- FileClose(f);
- end;
- end;
-
- procedure TQDB.UpdateNavigator;
- { recalcs BoF and EoF and then prompts the navigator (if there is }
- { one assigned) to update its buttons }
- var
- This: TItemIndex;
- begin
- if Filtered then
- begin
- if AggressiveUpdate then
- begin
- This := FItemIndex - 1;
- while (This >= 0) and not Matches(GetKey(This), FFilter) do
- begin
- dec(This);
- end;
- if This < 0 then
- begin
- FBoF := true;
- end;
- This := FItemIndex + 1;
- while (This < FCount) and not Matches(GetKey(This), FFilter) do
- begin
- inc(This);
- end;
- if This >= FCount then
- begin
- FEoF := true;
- end;
- end;
- end
- else
- begin
- FBoF := (FCount > 0) and (FItemIndex = 0);
- FEoF := (FCount > 0) and (FItemIndex + 1 = FCount);
- end;
- if Assigned(FQDBNavigator) then
- begin
- FQDBNavigator.QDBStateChanged;
- end;
- end;
-
- { Basically -- the TQDBNavigator component from DBCtrls but with }
- { all the BDE stuff torn out and replaced with QDB stuff instead. }
- { Portions of this code are Copyright Borland. }
- { Copyright (c) 1995-1997 Borland International. All Rights Reserved. }
-
- { TQDBNavigator }
-
- const
- InitRepeatPause = 400; { pause before repeat timer (ms) }
- RepeatPause = 100; { pause before hint window displays (ms)}
- SpaceSize = 5; { size of space between special buttons }
-
- const
- SFirstRecord = 119;
- SPrevRecord = 120;
- SNextRecord = 121;
- SLastRecord = 122;
- SInsertRecord = 123;
- SDeleteRecord = 124;
- SEditRecord = 125;
- SPostEdit = 126;
- SCancelEdit = 127;
- SRefreshRecord = 128;
- BtnTypeName: array[TNavigateBtn] of pchar = ('FIRST', 'PREV', 'NEXT',
- 'LAST', 'INSERT', 'DELETE', 'EDIT', 'POST', 'CANCEL', 'REFRESH');
- BtnHintId: array[TNavigateBtn] of word = (SFirstRecord, SPrevRecord,
- SNextRecord, SLastRecord, SInsertRecord, SDeleteRecord, SEditRecord,
- SPostEdit, SCancelEdit, SRefreshRecord);
-
- constructor TQDBNavigator.Create(AOwner: TComponent);
- begin
- inherited Create(AOwner);
- ControlStyle := ControlStyle - [csAcceptsControls, csSetCaption] + [csOpaque
- ];
- {$IFDEF VER100}
- if not NewStyleControls then
- ControlStyle := ControlStyle + [csFramed];
- {$ELSE}
- ControlStyle := ControlStyle + [csFramed];
- {$ENDIF}
- FVisibleButtons := [nbFirst, nbPrev, nbNext, nbLast, nbInsert, nbDelete, nbEdit, nbPost,
- nbCancel, nbRefresh];
- FHints := TStringList.Create;
- TStringList(FHints).OnChange := HintsChanged;
- InitButtons;
- BevelOuter := bvNone;
- BevelInner := bvNone;
- Width := 241;
- Height := 25;
- ButtonHeight := 0;
- ButtonWidth := 0;
- FocusedButton := nbFirst;
- end;
-
- destructor TQDBNavigator.Destroy;
- var
- i: TNavigateBtn;
- begin
- if FQDB <> nil then {BS}
- FQDB.SetLinkToNavigator(nil); {BS}
- FHints.Free;
- for i := Low(Buttons) to High(Buttons) do
- Buttons[i].Free;
- inherited Destroy;
- end;
-
- procedure TQDBNavigator._Click(Sender: TObject);
- begin
- BtnClick(TNavButton(Sender).Index);
- end;
-
- procedure TQDBNavigator.AdjustSize(var W: integer; var H: integer);
- var
- Count: integer;
- MinW: integer;
- MinH: integer;
- i: TNavigateBtn;
- Space, Temp, Remain: integer;
- X: integer;
- Y: integer;
- begin
- if (csLoading in ComponentState) then
- exit;
- if Buttons[nbFirst] = nil then
- exit;
- Count := 0;
- for i := Low(Buttons) to High(Buttons) do
- begin
- if Buttons[i].Visible then
- begin
- inc(Count);
- end;
- end;
- if Count = 0 then
- inc(Count);
-
- {horizontal}
- if ((FOrientation = noAuto) and (W >= H)) or (FOrientation = noHoriz) then
- begin
- MinW := Count * (MinBtnSize.X);
- if W < MinW then
- W := MinW;
- if H < MinBtnSize.Y then
- H := MinBtnSize.Y;
- ButtonWidth := ((W) div Count);
- Temp := Count * (ButtonWidth);
- if Align = alNone then
- W := Temp;
- X := 0;
- Remain := W - Temp;
- Temp := Count div 2;
- for i := Low(Buttons) to High(Buttons) do
- begin
- if Buttons[i].Visible then
- begin
- Space := 0;
- if Remain <> 0 then
- begin
- dec(Temp, Remain);
- if Temp < 0 then
- begin
- inc(Temp, Count);
- Space := 1;
- end;
- end;
- Buttons[i].SetBounds(X, 0, ButtonWidth + Space, Height);
- inc(X, ButtonWidth + Space);
- end
- else
- Buttons[i].SetBounds(Width, 0, ButtonWidth, Height);
- end;
- end {vertical: ((FDirection=dirAuto) and (W < H)) or (FDirection=dirVertical)}
- else
- begin
- MinH := Count * (MinBtnSize.Y);
- if H < MinH then
- H := MinH;
- if W < MinBtnSize.X then
- W := MinBtnSize.X;
- ButtonHeight := (H div Count);
- Temp := Count * (ButtonHeight);
- if Align = alNone then
- H := Temp;
- Y := 0;
- Remain := H - Temp;
- Temp := Count div 2;
- for i := Low(Buttons) to High(Buttons) do
- begin
- if Buttons[i].Visible then
- begin
- Space := 0;
- if Remain <> 0 then
- begin
- dec(Temp, Remain);
- if Temp < 0 then
- begin
- inc(Temp, Count);
- Space := 1;
- end;
- end;
- Buttons[i].SetBounds(0, Y, Width, ButtonHeight + Space);
- inc(Y, ButtonHeight + Space);
- end
- else
- Buttons[i].SetBounds(0, Height, ButtonHeight, Width);
- end;
- end;
- end;
-
- procedure TQDBNavigator.BtnClick(Index: TNavigateBtn);
- begin
- if (FQDB <> nil) then
- begin
- if not (csDesigning in ComponentState) and Assigned(FBeforeAction) then
- FBeforeAction(Self, Index);
- case Index of
- nbPrev:
- Prev;
- nbNext:
- Next;
- nbFirst:
- First;
- nbLast:
- Last;
- nbInsert:
- Insert;
- nbEdit:
- Edit;
- nbCancel:
- Cancel;
- nbPost:
- Post;
- nbRefresh:
- Refresh;
- nbDelete:
- Delete;
- end;
- end;
- if not (csDesigning in ComponentState) and Assigned(FOnNavClick) then
- FOnNavClick(Self, Index);
- end;
-
- procedure TQDBNavigator.BtnMouseDown(Sender: TObject; Button: TMouseButton;
- Shift: TShiftState; X, Y: integer);
- var
- OldFocus: TNavigateBtn;
- begin
- OldFocus := FocusedButton;
- FocusedButton := TNavButton(Sender).Index;
- if TabStop and (GetFocus <> Handle) and CanFocus then
- begin
- SetFocus;
- if (GetFocus <> Handle) then
- exit;
- end
- else
- if TabStop and (GetFocus = Handle) and (OldFocus <> FocusedButton) then
- begin
- Buttons[OldFocus].Invalidate;
- Buttons[FocusedButton].Invalidate;
- end;
- end;
-
- procedure TQDBNavigator.Cancel;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnCancel) then
- FOnCancel(Self, FQDB)
- else
- FQDB.Cancel;
- end;
-
- procedure TQDBNavigator.Delete;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnDelete) then
- FOnDelete(Self, FQDB)
- else
- FQDB.Delete;
- end;
-
- procedure TQDBNavigator.Edit;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnEdit) then
- FOnEdit(Self, FQDB)
- else
- FQDB.Edit;
- end;
-
- procedure TQDBNavigator.First;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnFirst) then
- FOnFirst(Self, FQDB)
- else
- FQDB.FirstItem;
- end;
-
- function TQDBNavigator.GetEnabled: boolean;
- begin
- Result := inherited Enabled;
- end;
-
- function TQDBNavigator.GetGlyph(Btn: TNavigateBtn): Graphics.TBitmap;
- begin
- Result := Buttons[Btn].Glyph;
- end;
-
- procedure TQDBNavigator.HintsChanged(Sender: TObject);
- begin
- InitHints;
- end;
-
- procedure TQDBNavigator.InitButtons;
- var
- i: TNavigateBtn;
- Btn: TNavButton;
- X: integer;
- ResName: array[0..40] of char;
- begin
- MinBtnSize := Point(20, 18);
- X := 0;
- for i := Low(Buttons) to High(Buttons) do
- begin
- Btn := TNavButton.Create(Self);
- {$IFDEF VER100}
- Btn.Flat := Flat;
- {$ENDIF}
- Btn.Index := i;
- Btn.Visible := i in FVisibleButtons;
- Btn.Enabled := true;
- Btn.SetBounds(X, 0, MinBtnSize.X, MinBtnSize.Y);
- StrFmt(ResName, 'qdb_%s', [BtnTypeName[i]]);
- Btn.Glyph.Handle := LoadBitmap(HInstance, ResName);
- Btn.NumGlyphs := 2;
- Btn.Enabled := false;
- Btn.Enabled := true;
- Btn.OnClick := _Click;
- Btn.OnMouseDown := BtnMouseDown;
- Btn.Parent := Self;
- Buttons[i] := Btn;
- X := X + MinBtnSize.X;
- end;
- InitHints;
- Buttons[nbPrev].NavStyle := Buttons[nbPrev].NavStyle + [nsAllowTimer];
- Buttons[nbNext].NavStyle := Buttons[nbNext].NavStyle + [nsAllowTimer];
- end;
-
- procedure TQDBNavigator.InitHints;
- var
- i: integer;
- j: TNavigateBtn;
- begin
- for j := Low(Buttons) to High(Buttons) do
- Buttons[j].Hint := LoadStr(BtnHintId[j]);
- j := Low(Buttons);
- for i := 0 to (FHints.Count - 1) do
- begin
- if FHints.Strings[i] <> '' then
- Buttons[j].Hint := FHints.Strings[i];
- if j = High(Buttons) then
- exit;
- inc(j);
- end;
- end;
-
- procedure TQDBNavigator.Insert;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnInsert) then
- FOnInsert(Self, FQDB)
- else
- FQDB.Insert;
- end;
-
- procedure TQDBNavigator.KeyDown(var Key: word; Shift: TShiftState);
- var
- NewFocus: TNavigateBtn;
- OldFocus: TNavigateBtn;
- begin
- OldFocus := FocusedButton;
- case Key of
- VK_RIGHT:
- begin
- NewFocus := FocusedButton;
- repeat
- if NewFocus < High(Buttons) then
- NewFocus := succ(NewFocus);
- until (NewFocus = High(Buttons)) or (Buttons[NewFocus].Visible);
- if NewFocus <> FocusedButton then
- begin
- FocusedButton := NewFocus;
- Buttons[OldFocus].Invalidate;
- Buttons[FocusedButton].Invalidate;
- end;
- end;
- VK_LEFT:
- begin
- NewFocus := FocusedButton;
- repeat
- if NewFocus > Low(Buttons) then
- NewFocus := pred(NewFocus);
- until (NewFocus = Low(Buttons)) or (Buttons[NewFocus].Visible);
- if NewFocus <> FocusedButton then
- begin
- FocusedButton := NewFocus;
- Buttons[OldFocus].Invalidate;
- Buttons[FocusedButton].Invalidate;
- end;
- end;
- VK_SPACE:
- begin
- if Buttons[FocusedButton].Enabled then
- Buttons[FocusedButton].Click;
- end;
- end;
- end;
-
- procedure TQDBNavigator.Last;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnLast) then
- FOnLast(Self, FQDB)
- else
- FQDB.LastItem;
- end;
-
- procedure TQDBNavigator.Loaded;
- var
- W, H: integer;
- begin
- inherited Loaded;
- W := Width;
- H := Height;
- AdjustSize(W, H);
- if (W <> Width) or (H <> Height) then
- inherited SetBounds(Left, Top, W, H);
- InitHints;
- end;
-
- procedure TQDBNavigator.Next;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnNext) then
- FOnNext(Self, FQDB)
- else
- FQDB.NextItem;
- end;
-
- procedure TQDBNavigator.Notification(AComponent: TComponent; Operation:
- TOperation);
- { if the link to a data file is broken we must respond }
- begin
- inherited Notification(AComponent, Operation);
- if (FQDB <> nil) and
- (AComponent = FQDB) and
- (Operation = opRemove) then
- FQDB := nil;
- end;
-
- procedure TQDBNavigator.Post;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnPost) then
- FOnPost(Self, FQDB)
- else
- FQDB.Post;
- end;
-
- procedure TQDBNavigator.Prev;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnPrev) then
- FOnPrev(Self, FQDB)
- else
- FQDB.PrevItem;
- end;
-
- procedure TQDBNavigator.QDBStateChanged;
- { update the buttons to reflect the state of the QDB }
- var
- Btn: TNavigateBtn;
- begin
- if not Assigned(FQDB) then
- exit;
- with FQDB do
- if not Ready then
- for Btn := Low(Buttons) to High(Buttons) do
- Buttons[Btn].Enabled := false
- else
- begin
- Buttons[nbFirst].Enabled := not BoF;
- Buttons[nbPrev].Enabled := not BoF;
- Buttons[nbNext].Enabled := not EoF;
- Buttons[nbLast].Enabled := not EoF;
- Buttons[nbInsert].Enabled := not (ReadOnly or FInserting);
- Buttons[nbDelete].Enabled := not (ReadOnly or (Count < 1));
- Buttons[nbEdit].Enabled := not (ReadOnly or FEditing or FInserting or (Count < 1));
- Buttons[nbPost].Enabled := FEditing or FInserting;
- Buttons[nbCancel].Enabled := FEditing or FInserting;
- Buttons[nbRefresh].Enabled := true;
- end;
- end;
-
- procedure TQDBNavigator.Refresh;
- begin
- if Assigned(FQDB) then
- if Assigned(FOnRefresh) then
- FOnRefresh(Self, FQDB)
- else
- FQDB.Refresh;
- end;
-
- procedure TQDBNavigator.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
- var
- W, H: integer;
- begin
- W := AWidth;
- H := AHeight;
- if not HandleAllocated then
- AdjustSize(W, H);
- inherited SetBounds(ALeft, ATop, W, H);
- end;
-
- procedure TQDBNavigator.SetEnabled(Value: boolean);
- { en/disable the buttons as well }
- var
- Btn: TNavigateBtn;
- begin
- if Value and not (csDesigning in ComponentState) then
- QDBStateChanged
- else
- for Btn := Low(Buttons) to High(Buttons) do
- Buttons[Btn].Enabled := Value;
- inherited Enabled := Value;
- end;
-
- procedure TQDBNavigator.SetFlat(Value: boolean);
- var
- i: TNavigateBtn;
- begin
- {$IFDEF VER100}
- if FFlat <> Value then
- begin
- FFlat := Value;
- for i := Low(Buttons) to High(Buttons) do
- Buttons[i].Flat := Value;
- end;
- {$ELSE}
- FFlat := false;
- {$ENDIF}
- end;
-
- procedure TQDBNavigator.SetGlyph(Btn: TNavigateBtn; Value: Graphics.TBitmap
- );
- begin
- Buttons[Btn].Glyph := Value;
- end;
-
- procedure TQDBNavigator.SetHints(Value: TStrings);
- begin
- FHints.Assign(Value);
- InitHints;
- end;
-
- procedure TQDBNavigator.SetOrientation(Value: TNavOrientation);
- var
- W, H: integer;
- begin
- W := Width;
- H := Height;
- if ((((FOrientation = noAuto) and (W >= H)) or (FOrientation = noHoriz
- )) and (Value = noVert)) or
- ((((FOrientation = noAuto) and (W < H)) or (FOrientation = noVert)
- ) and (Value = noHoriz)) then
- begin
- W := Height;
- H := Width;
- end;
- FOrientation := Value;
- AdjustSize(W, H);
- if (W <> Width) or (H <> Height) then
- inherited SetBounds(Left, Top, W, H);
- Invalidate;
- end;
-
- procedure TQDBNavigator.SetQDB(Value: TQDB);
- begin
- if Value <> FQDB then
- begin
- if FQDB <> nil then {BS}
- FQDB.SetLinkToNavigator(nil); {BS}
- FQDB := Value;
- if FQDB <> nil then {BS}
- FQDB.SetLinkToNavigator(TQDBNavigator(Self));
- end;
- end;
-
- procedure TQDBNavigator.SetVisible(Value: TButtonSet);
- var
- i: TNavigateBtn;
- W, H: integer;
- begin
- W := Width;
- H := Height;
- FVisibleButtons := Value;
- for i := Low(Buttons) to High(Buttons) do
- Buttons[i].Visible := i in FVisibleButtons;
- AdjustSize(W, H);
- if (W <> Width) or (H <> Height) then
- inherited SetBounds(Left, Top, W, H);
- Invalidate;
- end;
-
- procedure TQDBNavigator.WMGetDlgCode(var Message: TWMGetDlgCode);
- begin
- Message.Result := DLGC_WANTARROWS;
- end;
-
- procedure TQDBNavigator.WMKillFocus(var Message: TWMKillFocus);
- begin
- Buttons[FocusedButton].Invalidate;
- end;
-
- procedure TQDBNavigator.WMSetFocus(var Message: TWMSetFocus);
- begin
- Buttons[FocusedButton].Invalidate;
- end;
-
- procedure TQDBNavigator.WMSize(var Message: TWMSize);
- var
- W, H: integer;
- begin
- inherited;
- { check for minimum size }
- W := Width;
- H := Height;
- AdjustSize(W, H);
- if (W <> Width) or (H <> Height) then
- inherited SetBounds(Left, Top, W, H);
- Message.Result := 0;
- end;
-
- { TNavButton }
-
- destructor TNavButton.Destroy;
- begin
- if FRepeatTimer <> nil then
- FRepeatTimer.Free;
- inherited Destroy;
- end;
-
- procedure TNavButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
- X, Y: integer);
- begin
- inherited MouseDown(Button, Shift, X, Y);
- if nsAllowTimer in FNavStyle then
- begin
- if FRepeatTimer = nil then
- FRepeatTimer := TTimer.Create(Self);
-
- FRepeatTimer.OnTimer := TimerExpired;
- FRepeatTimer.Interval := InitRepeatPause;
- FRepeatTimer.Enabled := true;
- end;
- end;
-
- procedure TNavButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
- X, Y: integer);
- begin
- inherited MouseUp(Button, Shift, X, Y);
- if FRepeatTimer <> nil then
- FRepeatTimer.Enabled := false;
- end;
-
- procedure TNavButton.Paint;
- var
- R: TRect;
- begin
- inherited Paint;
- if (GetFocus = Parent.Handle) and
- (FIndex = TQDBNavigator(Parent).FocusedButton) then
- begin
- R := Bounds(0, 0, Width, Height);
- InflateRect(R, -3, -3);
- if FState = bsDown then
- OffsetRect(R, 1, 1);
- DrawFocusRect(Canvas.Handle, R);
- end;
- end;
-
- procedure TNavButton.TimerExpired(Sender: TObject);
- begin
- FRepeatTimer.Interval := RepeatPause;
- if (FState = bsDown) and MouseCapture then
- begin
- try
- Click;
- except
- FRepeatTimer.Enabled := false;
- raise;
- end;
- end;
- end;
-
- initialization
-
- QDBTempFileLocation := '';
-
- end.
-
-