{ matortheeternal's Functions edited 8/28/2015 A set of useful functions for use in TES5Edit scripts. **LIST OF INCLUDED FUNCTIONS** - [GetVersionString]: gets TES5Edit's version as a string. - [ColorToInt]: gets an integer value representing a color from a TColor record. - [ElementTypeString]: uses ElementType and outputs a string. - [DefTypeString]: uses DefType and outputs a string. - [ConflictThisString]: uses ConflictThisForNode or ConflictThisForMainRecord and outputs a string. - [ConflictAllString]: uses ConflictAllForNode or ConflictAllForMainRecord and outputs a string. - [FreeAndNil]: frees and nils an object reference. - [IsDirectoryEmpty]: returns true if a directory is empty. False otherwise. - [Matches]: returns true or false on whether or not an input string matches a basic regular expression (e.g. *.esp) - [wCopyFile]: copies a file using ShellExecute with cmd. Would be superior to CopyFile if it was synchronous, but it isn't yet. - [CopyDirectory]: recursively copies the contents of a directory to a new destination path. - [BatchCopyDirectory]: batch variant of CopyDirectory. - [DeleteDirectory]: deletes the contents of a directory, and optionally the directory itself. - [RecursiveFileSearch]: recursively searches for a file in all the folders at a path. Returns the path of the first file matching the given filename, if it is found. - [SanitizeFileName]: removes characters not allowed in filenames from a string. - [BoolToStr]: converts a boolean value to a string. - [TimeStr]: returns a time string from a TDateTime. - [FileDateTimeStr]: returns a filename-safe DateTime string from a TDateTime. - [ReverseString]: reverses a string. - [StrEndsWith]: checks if a string ends with a substring. - [RemoveFromEnd]: removes a substring from the end of a string, if found. - [AppendIfMissing]: appends a substring to the end of a string, if it's not already there. - [ItPos]: finds the position of an iteration of a substring in a string. - [rPos]: finds the last position of a substring in a string by starting at the end of the string and moving to the front. - [CopyFromTo]: copies all characters in a string from a starting position to an ending position. - [SetChar]: Sets a character in a string to a different character and returns the resulting string. - [GetChar]: Gets a character in a string and returns it. - [DelimitedTextBetween]: Gets the delimited text of a stringlist between two indexes, including the entries at those indices. - [GetTextIn]: Gets a substring from a string between two characters. - [RecordByHexFormID]: Gets a record by a hexadecimal FormID string. - [GetAuthor]: Gets the author of a file. - [SetAuthor]: Sets the author of a file. - [FileByName]: gets a file from a filename. - [FileByAuthor]: gets a file from an author. - [OverrideByFile]: gets the override for a particular record in a particular file, if it exists. - [OverrideRecordCount]: gets the number of override records in a file or record group. - [GetRecords]: adds the records in a file or group to a stringlist. - [GroupSignature]: gets the signature of a group record. - [HexFormID]: gets the FormID of a record as a hexadecimal string. - [FileFormID]: gets the FileFormID of a record as a cardinal. - [IsLocalRecord]: returns false for override and injected records. - [IsOverrideRecord]: returns true for override records. - [SmallName]: gets the FormID and editor ID as a string. - [ElementByIP]: loads an element by an indexed path. - [ElementsByMIP]: provides an array of elements matching an indexed path (with [*] meaning any index). - [IndexedPath]: gets the indexed path of an element. - [ElementPath]: gets the path of an element that can then be used in ElementByPath. - [SetListEditValues]: sets the edit values in a list of elements to the values stored in a stringlist. - [SetListNativeValues]: sets the native values in a list of elements to the values stored in a TList. - [ebn]: ElementByName shortened function name. - [ebp]: ElementByPath shortened function name. - [ebi]: ElementByIndex shortened function name. - [ebip]: ElementByIP shortened function name. - [mgeev]: Produces a stringlist of element edit values from a list of elements. Use with ElementsByMIP. - [geev]: GetElementEditValues enhanced with ElementByIP. - [genv]: GetElementNativeValues enhanced with ElementByIP. - [seev]: SetElementEditValues enhanced with ElementByIP. - [senv]: SetElementNativeValues enhanced with ElementByIP. - [slev]: SetListEditValues shortened function name. - [slnv]: SetListNativeValues shortened function name. - [gav]: GetAllValues returns a string of all of the values in an element. - [IsD]: GetIsDeleted shortened function name. - [IsID]: GetIsInitiallyDisabled shortened function name. - [IsP]: GetIsPersistent shortened function name. - [IsVWD]: GetIsVisibleWhenDistant shortened function name. - [SetD]: SetIsDeleted shortened function name. - [SetID]: SetIsInitiallyDisabled shortened function name. - [SetP]: SetIsPersistent shortened function name. - [SetVWD]: SetIsVisibleWhenDistant shortened function name. - [HasKeyword]: checks if a record has a keyword matching the input EditorID. - [HasItem]: checks if a record has an item matching the input EditorID. - [HasPerkCondition]: checks if a record has a perk condition for a perk matching the input EditorID. - [ExtractBSA]: extracts the contents of a BSA to the specified path. - [ExtractPathBSA]: extracts the contents of a BSA from a specified subpath to the specified path. - [PrintBSAContents]: prints the contents of a BSA to xEdit's message log. - [AddMastersToList]: adds masters from the specified file (and the file itself) to the specified stringlist. - [AddMastersToFile]: adds masters to the specified file from the specified stringlist. Will re-add masters if they were already added by AddMasterIfMissing and later removed. - [RemoveMaster]: removes a master of the specified name from the specified file. NOTE: This function can be dangerous if used improperly. - [FileSelect]: creates a window from which the user can select or create a file. Doesn't include bethesda master files. Outputs selected file as IInterface. - [MultiFileSelect]: allows the user to select multiple files, returning them through a TStringList. - [RecordSelect]: creates a window from which the user can choose a record. - [EditOutOfDate]: alerts the user that their xEdit is out of date, and provides them with a button they can click to go to the AFKMods page to download an updated version. - [BoolToChecked]: converts a boolean to a TCheckBoxState value. - [CheckedToBool]: converts TCheckBoxState value to boolean. - [ConstructGroup]: an all-in-one group box constructor. - [ConstructImage]: an all-in-one image constructor. - [ConstructRadioGroup]: an all-in-one radiogroup constructor. - [ConstructRadioButton]: an all-in-one radiobutton constructor. - [ConstructMemo]: an all-in-one memo constructor. - [ConstructScrollBox]: an all-in-one scrollbox constructor. - [ConstructCheckBox]: an all-in-one checkbox constructor. - [ConstructLabel]: an all-in-one label constructor. - [ConstructEdit]: an all-in-one edit constructor. - [ConstructButton]: an all-in-one button constructor. - [ConstructLabelEditPair]: constructs a TLabel and TEdit side-by-side. - [ConstructModalButtons]: a procedure to make the standard OK and Cancel buttons on a form. - [cGroup]: ConstructGroup shortened function name. - [cImage]: ConstructImage shortened function name. - [cRadioGroup]: ConstructRadioGroup shortened function name. - [cRadioButton]: ConstructRadioButton shortened function name. - [cMemo]: ConstructMemo shortened function name. - [cScrollBox]: ConstructScrollBox shortened function name. - [cCheckBox]: ConstructCheckBox shortened function name. - [cLabel]: ConstructLabel shortened function name. - [cEdit]: ConstructEdit shortened function name. - [cButton]: ConstructButton shortened function name. - [cPair]: ConstructLabelEditPair shortened function name. - [cModal]: ConstructModalButtons shortened function name. } unit mteFunctions; const bethesdaFiles = 'Skyrim.esm'#13'Update.esm'#13'Dawnguard.esm'#13'HearthFires.esm'#13 'Dragonborn.esm'#13'Fallout3.esm'#13'FalloutNV.esm'#13'Oblivion.esm'#13 'Skyrim.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#13 'Fallout3.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#13 'Oblivion.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'#13 'FalloutNV.Hardcoded.keep.this.with.the.exe.and.otherwise.ignore.it.I.really.mean.it.dat'; GamePath = DataPath + '..\'; type TColor = Record red, green, blue: integer; end; var sFiles, sGroups, sRecords: string; { GetVersionString: Gets TES5Edit's version as a string. Will throw an exception on versions < 3.0.31, so surround in a try..except block if you want your script to terminate gracefully on old versions. Example usage: s := GetVersionString(wbVersionNumber); AddMessage(s); // xEdit version *.*.* } function GetVersionString(v: integer): string; begin Result := Format('%sEdit version %d.%d.%d', [ wbAppName, v shr 24, v shr 16 and $FF, v shr 8 and $FF ]); end; { ColorToInt: Gets an integer value representing a color from a TColor record. Example usage: color.Red := $FF; color.Green := $FF; color.Blue := $FF; c := ColorToInt(color.Red, color.Green, color.Blue); } function ColorToInt(red: integer; green: integer; blue: integer): integer; begin Result := blue * 65536 + green * 256 + red; end; { ElementTypeString: Uses ElementType and outputs a string. Example usage: element := ElementByPath(e, 'KWDA'); AddMessage(ElementTypeString(element)); } function ElementTypeString(e: IInterface): string; begin Result := ''; if ElementType(e) = etFile then Result := 'etFile' else if ElementType(e) = etMainRecord then Result := 'etMainRecord' else if ElementType(e) = etGroupRecord then Result := 'etGroupRecord' else if ElementType(e) = etSubRecord then Result := 'etSubRecord' else if ElementType(e) = etSubRecordStruct then Result := 'etSubRecordStruct' else if ElementType(e) = etSubRecordArray then Result := 'etSubRecordArray' else if ElementType(e) = etSubRecordUnion then Result := 'etSubRecordUnion' else if ElementType(e) = etArray then Result := 'etArray' else if ElementType(e) = etStruct then Result := 'etStruct' else if ElementType(e) = etValue then Result := 'etValue' else if ElementType(e) = etFlag then Result := 'etFlag' else if ElementType(e) = etStringListTerminator then Result := 'etStringListTerminator' else if ElementType(e) = etUnion then Result := 'etUnion'; end; { DefTypeString: Uses DefType and outputs a string. Example usage: element := ElementByPath(e, 'KWDA'); AddMessage(DefTypeString(element)); } function DefTypeString(e: IInterface): string; begin Result := ''; if DefType(e) = dtRecord then Result := 'dtRecord' else if DefType(e) = dtSubRecord then Result := 'dtSubRecord' else if DefType(e) = dtSubRecordArray then Result := 'dtSubRecordArray' else if DefType(e) = dtSubRecordStruct then Result := 'dtSubRecordStruct' else if DefType(e) = dtSubRecordUnion then Result := 'dtSubRecordUnion' else if DefType(e) = dtString then Result := 'dtString' else if DefType(e) = dtLString then Result := 'dtLString' else if DefType(e) = dtLenString then Result := 'dtLenString' else if DefType(e) = dtByteArray then Result := 'dtByteArray' else if DefType(e) = dtInteger then Result := 'dtInteger' else if DefType(e) = dtIntegerFormater then Result := 'dtIntegerFormatter' else if DefType(e) = dtFloat then Result := 'dtFloat' else if DefType(e) = dtArray then Result := 'dtArray' else if DefType(e) = dtStruct then Result := 'dtStruct' else if DefType(e) = dtUnion then Result := 'dtUnion' else if DefType(e) = dtEmpty then Result := 'dtEmpty'; end; { ConflictThisString: Uses ConflictThisForNode or ConflictThisForMainRecord and outputs a string. Example usage: e := RecordByIndex(FileByIndex(0), 1); AddMessage(ConflictThisString(e)); } function ConflictThisString(e: IInterface): string; begin Result := ''; if ElementType(e) = etMainRecord then begin if ConflictThisForMainRecord(e) = ctUnknown then Result := 'ctUnknown' else if ConflictThisForMainRecord(e) = ctIgnored then Result := 'ctIgnored' else if ConflictThisForMainRecord(e) = ctNotDefined then Result := 'ctNotDefined' else if ConflictThisForMainRecord(e) = ctIdenticalToMaster then Result := 'ctIdenticalToMaster' else if ConflictThisForMainRecord(e) = ctOnlyOne then Result := 'ctOnlyOne' else if ConflictThisForMainRecord(e) = ctHiddenByModGroup then Result := 'ctHiddenByModGroup' else if ConflictThisForMainRecord(e) = ctMaster then Result := 'ctMaster' else if ConflictThisForMainRecord(e) = ctConflictBenign then Result := 'ctConflictBenign' else if ConflictThisForMainRecord(e) = ctOverride then Result := 'ctOverride' else if ConflictThisForMainRecord(e) = ctIdenticalToMasterWinsConflict then Result := 'ctIdenticalToMasterWinsConflict' else if ConflictThisForMainRecord(e) = ctConflictWins then Result := 'ctConflictWins' else if ConflictThisForMainRecord(e) = ctConflictLoses then Result := 'ctConflictLoses'; end else begin if ConflictThisForNode(e) = ctUnknown then Result := 'ctUnknown' else if ConflictThisForNode(e) = ctIgnored then Result := 'ctIgnored' else if ConflictThisForNode(e) = ctNotDefined then Result := 'ctNotDefined' else if ConflictThisForNode(e) = ctIdenticalToMaster then Result := 'ctIdenticalToMaster' else if ConflictThisForNode(e) = ctOnlyOne then Result := 'ctOnlyOne' else if ConflictThisForNode(e) = ctHiddenByModGroup then Result := 'ctHiddenByModGroup' else if ConflictThisForNode(e) = ctMaster then Result := 'ctMaster' else if ConflictThisForNode(e) = ctConflictBenign then Result := 'ctConflictBenign' else if ConflictThisForNode(e) = ctOverride then Result := 'ctOverride' else if ConflictThisForNode(e) = ctIdenticalToMasterWinsConflict then Result := 'ctIdenticalToMasterWinsConflict' else if ConflictThisForNode(e) = ctConflictWins then Result := 'ctConflictWins' else if ConflictThisForNode(e) = ctConflictLoses then Result := 'ctConflictLoses'; end; end; { ConflictAllString: Uses ConflictAllForNode or ConflictAllForMainRecord and outputs a string. Example usage: e := RecordByIndex(FileByIndex(0), 1); AddMessage(ConflictAllString(e)); } function ConflictAllString(e: IInterface): string; begin Result := ''; if ElementType(e) = etMainRecord then begin if ConflictAllForMainRecord(e) = caUnknown then Result := 'caUnknown' else if ConflictAllForMainRecord(e) = caOnlyOne then Result := 'caOnlyOne' else if ConflictAllForMainRecord(e) = caConflict then Result := 'caConflict' else if ConflictAllForMainRecord(e) = caNoConflict then Result := 'caNoConflict' else if ConflictAllForMainRecord(e) = caConflictBenign then Result := 'caConflictBenign' else if ConflictAllForMainRecord(e) = caOverride then Result := 'caOverride' else if ConflictAllForMainRecord(e) = caConflictCritical then Result := 'caConflictCritical'; end else begin if ConflictAllForNode(e) = caUnknown then Result := 'caUnknown' else if ConflictAllForNode(e) = caOnlyOne then Result := 'caOnlyOne' else if ConflictAllForNode(e) = caConflict then Result := 'caConflict' else if ConflictAllForNode(e) = caNoConflict then Result := 'caNoConflict' else if ConflictAllForNode(e) = caConflictBenign then Result := 'caConflictBenign' else if ConflictAllForNode(e) = caOverride then Result := 'caOverride' else if ConflictAllForNode(e) = caConflictCritical then Result := 'caConflictCritical'; end; end; { FreeAndNil: Frees and nils an object reference. Usage: sl := TStringList.Create; sl.Add('Hi'); FreeAndNil(sl); } procedure FreeAndNil(var ObjectReference: TObject); begin if Assigned(ObjectReference) then begin ObjectReference.Free; ObjectReference := nil; end; end; { IsDirectoryEmpty: Checks if a given directory is empty. Example usage: if not IsDirectoryEmpty(ScriptsPath) then AddMessage('You have scripts! That''s good.'); } function IsDirectoryEmpty(const directory: string): boolean; var searchRec: TSearchRec; begin try result := (FindFirst(directory+'\*.*', faAnyFile, searchRec) = 0) AND (FindNext(searchRec) = 0) AND (FindNext(searchRec) <> 0); finally FindClose(searchRec) ; end; end; { Matches: Checks if an input string matches a basic regex input. Example usage: if Matches('This.is.a.test.bak', 'This.*.*.*.bak') then AddMessage('Works!'); } function Matches(input, expression: string): boolean; var slExpr: TStringList; regex: TRegEx; pPos, i: integer; begin Result := false; // use stringlist to determine if input matches expression slExpr := TStringList.Create; slExpr.Delimiter := '*'; slExpr.StrictDelimiter := true; slExpr.DelimitedText := expression; for i := Pred(slExpr.Count) downto 0 do begin if slExpr[i] = '' then slExpr.Delete(i); end; if Pos('*', expression) > 0 then begin pPos := 0; for i := 0 to Pred(slExpr.Count) do begin if Pos(slExpr[i], input) > pPos then begin pPos := Pos(slExpr[i], input); input := Copy(input, Pos(slExpr[i], input) + Length(slExpr[i]) + 1, Length(input)); end else break; if i = Pred(slExpr.Count) then Result := true; end; end else Result := (input = expression); end; { wCopyFile: Copies a file using windows (cmd) via ShellExecute to avoid memory leaks associated with using the pascal CopyFile routine. Example usage: wCopyFile(GamePath + 'Skyrim.exe', '%UserProfile%\Desktop\Skyrim.exe.bak'); } procedure wCopyFile(src, dst: string; silent: boolean); begin if not silent then AddMessage('Copying '+src+' to '+dst); ShellExecute(TForm(frmMain).Handle, 'open', 'cmd', '/C copy /Y "'+src+'" "'+dst+'"', ExtractFilePath(src), SW_HIDE); end; { CopyDirectory: Recursively copies all of the contents of a directory. Example usage: slIgnore := TStringList.Create; slIgnore.Add('mteFunctions.pas'); CopyDirectory(ScriptsPath, 'C:\ScriptsBackup', slIgnore); } procedure CopyDirectory(src, dst: string; ignore: TStringList; verbose: boolean); var i: integer; rec: TSearchRec; skip: boolean; begin src := AppendIfMissing(src, '\'); dst := AppendIfMissing(dst, '\'); if FindFirst(src + '*', faAnyFile, rec) = 0 then begin repeat skip := false; for i := 0 to Pred(ignore.Count) do begin skip := Matches(Lowercase(rec.Name), ignore[i]); if (Pos('.', ignore[i]) > 0) and ((rec.attr and faDirectory) = faDirectory) then skip := false; if (rec.Name = '.') or (rec.Name = '..') then skip := true; if skip and verbose then AddMessage(' Skipping '+rec.Name+', matched '+ignore[i]); if skip then break; end; if not skip then begin ForceDirectories(dst); if (rec.attr and faDirectory) <> faDirectory then begin if verbose then AddMessage(' Copying file from '+src+rec.Name+' to '+dst+rec.Name); CopyFile(PChar(src+rec.Name), PChar(dst+rec.Name), false); end else CopyDirectory(src+rec.Name, dst+rec.Name, ignore, verbose); end; until FindNext(rec) <> 0; FindClose(rec); end; end; { BatchCopyDirectory: Adds copy commands to a batch stringlist to copy all of the contents of a directory. Example usage: slIgnore := TStringList.Create; batch := TStringList.Create; slIgnore.Add('mteFunctions.pas'); BatchCopyDirectory(ScriptsPath, 'C:\ScriptsBackup', batch, slIgnore, false); } procedure BatchCopyDirectory(src, dst: string; ignore: TStringList; var batch: TStringList; verbose: boolean); var i: integer; rec: TSearchRec; skip: boolean; begin src := AppendIfMissing(src, '\'); dst := AppendIfMissing(dst, '\'); if FindFirst(src + '*', faAnyFile, rec) = 0 then begin repeat skip := false; for i := 0 to Pred(ignore.Count) do begin skip := Matches(Lowercase(rec.Name), ignore[i]); if (Pos('.', ignore[i]) > 0) and ((rec.attr and faDirectory) = faDirectory) then skip := false; if (rec.Name = '.') or (rec.Name = '..') then skip := true; if skip and verbose then AddMessage(' Skipping '+rec.Name+', matched '+ignore[i]); if skip then break; end; if not skip then begin ForceDirectories(dst); if (rec.attr and faDirectory) <> faDirectory then begin if verbose then AddMessage(' Copying file from '+src+rec.Name+' to '+dst+rec.Name); batch.Add('copy /Y "'+src+rec.Name+'" "'+dst+rec.Name+'"'); end else BatchCopyDirectory(src+rec.Name, dst+rec.Name, ignore, batch, verbose); end; until FindNext(rec) <> 0; FindClose(rec); end; end; { DeleteDirectory: Recursively deletes a directory and its contents. Example usage: DeleteDirectory(ScriptsPath + 'mp\temp\', true); } function DeleteDirectory(src: string; onlyChildren: boolean): boolean; const debug = false; var rec: TSearchRec; begin // exit early if directory doesn't exist if not DirectoryExists(src) then exit; try // loop through files in directory if FindFirst(src + '*', faAnyFile, rec) = 0 then begin repeat // do not try to recurse into . or .. else bad things happen. if (rec.name <> '.') and (rec.name <> '..') then begin if (rec.attr and faDirectory) <> faDirectory then begin // delete files if debug then AddMessage('Deleting file '+src+rec.name); DeleteFile(src + rec.name); end else begin // recurse to delete directories in the directory if debug then AddMessage('Deleting directory '+src+rec.name+'\'); DeleteDirectory(src + rec.name + '\', false); end; end; until FindNext(rec) <> 0; FindClose(rec); end; // remove directory if onlyChildren is false if not onlyChildren then RemoveDir(src); Result := true; except on Exception do Result := false; end; end; { RecursiveFileSearch: Recursively searches a path for a file matching aFileName, ignoring directories in the ignore TStringList, and not traversing deeper than maxDepth. Example usage: ignore := TStringList.Create; ignore.Add('Data'); p := RecursiveFileSearch('Skyrim.exe', GamePath, ignore, 1, false); AddMessage(p); } function RecursiveFileSearch(aPath, aFileName: string; ignore: TStringList; \ maxDepth: integer; verbose: boolean): string; var skip: boolean; i: integer; rec: TSearchRec; backslash: string; begin Result := ''; aPath := AppendIfMissing(aPath, '\'); if Result <> '' then exit; // always ignore . and .. ignore.Add('.'); ignore.Add('..'); if FindFirst(aPath + '*', faAnyFile, rec) = 0 then begin repeat skip := false; for i := 0 to Pred(ignore.Count) do begin skip := Matches(Lowercase(rec.Name), ignore[i]); if skip then break; end; if not skip then begin if ((rec.attr and faDirectory) = faDirectory) and (maxDepth > 0) then begin if verbose then AddMessage(' Searching directory '+aPath+rec.Name); Result := RecursiveFileSearch(aPath+rec.Name, aFileName, ignore, maxDepth - 1, verbose); end else if (rec.Name = aFileName) then Result := aPath + rec.Name; end; if (Result <> '') then break; until FindNext(rec) <> 0; FindClose(rec); end; end; { SanitizeFileName: Removes characters not allowed in filenames from a string. Example usage: today := Now; fn := 'merge_'+DateToStr(today)+'_'+TimeToStr(today)+'.txt'; fn := SanitizeFileName(fn); } function SanitizeFileName(fn: string): string; const badChars = '<>:"/\|?*'; var ch: char; i: integer; begin Result := fn; for i := Length(Result) - 1 downto 0 do begin ch := GetChar(Result, i); if (Pos(ch, badChars) > 0) or (Ord(ch) < 32) then SetChar(Result, i, ''); end; end; { TimeStr: Converts the time portion of a TDateTime to a string. Example usage: AddMessage(TimeStr(Now)); // 02:50:54 } function TimeStr(t: TDateTime): string; begin Result := FormatDateTime('hh:nn:ss', t); end; { FileDateTimeStr: Converts a TDateTime to a filename-safe string. Example usage: AddMessage(FileDateTimeStr(Now)); // } function FileDateTimeStr(t: TDateTime): string; begin Result := FormatDateTime('mmddyy_hhnnss', t); end; { BoolToStr: Converts a boolean value into a string. Example usage: b := True; AddMessage(BoolToStr(b)); // True } function BoolToStr(b: boolean): string; begin if b then Result := 'True' else Result := 'False'; end; { ReverseString: Reverses a string. This function will allow you to quickly reverse a string. Example usage: s := 'backwards'; s := ReverseString(s); AddMessage(s); // 'sdrawkcab' } function ReverseString(var s: string): string; var i: integer; begin Result := ''; for i := Length(s) downto 1 do begin Result := Result + Copy(s, i, 1); end; end; { StrEndsWith: Checks to see if a string ends with an entered substring. Example usage: s := 'This is a sample string.'; if StrEndsWith(s, 'string.') then AddMessage('It works!'); } function StrEndsWith(s1, s2: string): boolean; var i, n1, n2: integer; begin Result := false; n1 := Length(s1); n2 := Length(s2); if n1 < n2 then exit; Result := (Copy(s1, n1 - n2 + 1, n2) = s2); end; { RemoveFromEnd: Removes s1 from the end of s2, if found. Example usage: s := 'This is a sample string.'; AddMessage(RemoveFromEnd(s, 'string.')); //'This is a sample ' } function RemoveFromEnd(s1, s2: string): string; begin Result := s1; if StrEndsWith(s1, s2) then Result := Copy(s1, 1, Length(s1) - Length(s2)); end; { AppendIfMissing: Appends s2 to the end of s1 if it's not already there. Example usage: s := 'This is a sample string.'; AddMessage(AppendIfMissing(s, 'string.')); //'This is a sample string.' AddMessage(AppendIfMissing(s, ' Hello.')); //'This is a sample string. Hello.' } function AppendIfMissing(s1, s2: string): string; begin Result := s1; if not StrEndsWith(s1, s2) then Result := s1 + s2; end; { ItPos: An iteration position function. This function will allow you to find the position of a substring in a string, or the position of the second, third, etc. iterations of that substring. If the iteration of the substring isn't found -1 is returned. Example usage: s := '10101'; k := ItPos('1', s, 3); AddMessage(IntToStr(k)); // 5 } function ItPos(substr: string; str: string; it: integer): integer; var i, found: integer; begin Result := -1; //AddMessage('Called ItPos('+substr+', '+str+', '+IntToStr(it)+')'); if it = 0 then exit; found := 0; for i := 1 to Length(str) do begin //AddMessage(' Scanned substring: '+Copy(str, i, Length(substr))); if (Copy(str, i, Length(substr)) = substr) then begin //AddMessage(' Matched substring, iteration #'+IntToStr(found + 1)); Inc(found); end; if found = it then begin Result := i; Break; end; end; end; { rPos: A reverse position function. This function will allow you to find the last position of a substring in a string. Example usage: s := 'C:\SomePath\to\a\file.txt'; AddMessage(Copy(s, rPos('\', s) + 1, Length(s))); } function rPos(substr, str: string): integer; var i: integer; begin Result := -1; if (Length(str) - Length(substr) < 0) then exit; for i := Length(str) - Length(substr) downto 1 do begin if (Copy(str, i, Length(substr)) = substr) then begin Result := i; break; end; end; end; { CopyFromTo: A copy function that allows you to copy from one position to another. This function is a better copy function, in my opinion. Example usage: s := 'Hi. I'm a cool guy.'; s := CopyFromTo(s, Pos('a', s), Pos('g', s)); AddMessage(s); //'a cool g' } function CopyFromTo(s: string; p1: integer; p2: integer): string; begin Result := ''; if p1 > p2 then exit; Result := Copy(s, p1, p2 - p1 + 1); end; { SetChar: Sets a character in a string to a different character and returns the resulting string. Example usage: s := '1234'; SetChar(s, 2, 'A'); AddMessage(s); //'1A34' } procedure SetChar(var input: string; n: integer; c: char); var front, back: string; begin front := Copy(input, 1, n - 1); back := Copy(input, n + 1, Length(input)); input := front + c + back; end; { GetChar: Gets a character in a string and returns it. Example usage: s := '1234'; AddMessage(GetChar(s, 3)); //'3' } function GetChar(const s: string; n: integer): char; begin Result := Copy(s, n, 1); end; { DelimitedTextBetween: Gets the delimited text from a stringlist @sl between index @first and ending at index @last. Example usage: s := 'Items\[0]\CNTO - Item\Item'; path := TStringList.Create; path.Delimiter := '\'; path.StrictDelimiter := true; path.DelimitedText := s; AddMessage(Delimitedtext(path, 2, Pred(path.count))); // prints 'CNTO - Item\Item' } function DelimitedTextBetween(var sl: TStringList; first, last: integer): string; var i: integer; begin Result := ''; for i := first to last do begin Result := Result + sl[i]; if i < last then Result := Result + sl.Delimiter; end; end; { GetTextIn: Returns a substring of @str between characters @open and @close. Example usage: s := 'Hello [test] world'; AddMessage(GetTextIn(s, '[', ']')); // prints 'test' } function GetTextIn(str: string; open, close: char): string; var i, openIndex: integer; bOpen: boolean; begin Result := ''; bOpen := false; for i := 0 to Length(str) do begin if not bOpen and (GetChar(str, i) = open) then begin openIndex := i; bOpen := true; end; if bOpen and (GetChar(str, i) = close) then begin Result := CopyFromTo(str, openIndex + 2, i - 1); break; end; end; end; { RecordByHexFormID: Gets a record by its hexadecimal FormID. Example usage: e := RecordByHexFormID('0002BFA2'); AddMessage(Name(e)); } function RecordByHexFormID(id: string): IInterface; var f: IInterface; begin f := FileByLoadOrder(StrToInt('$' + Copy(id, 1, 2))); Result := RecordByFormID(f, StrToInt('$' + id), true); end; { GetAuthor: Gets the author field from a file. Example usage: f := FileByName('Dragonborn.esm'); AddMessage(GetAuthor(f)); // rsalvatore } function GetAuthor(f: IInterface): string; var fileHeader: IInterface; begin fileHeader := ElementByIndex(f, 0); Result := geev(fileHeader, 'CNAM - Author'); end; { SetAuthor: Sets the author field of a file. Example usage: f := FileByName('Dragonborn.esm'); SetAuthor(f, 'George'); } procedure SetAuthor(f: IInterface; author: string); var fileHeader: IInterface; begin fileHeader := ElementByIndex(f, 0); seev(fileHeader, 'CNAM - Author', author); end; { FileByName: Gets a file from a filename. Example usage: f := FileByName('Skyrim.esm'); } function FileByName(s: string): IInterface; var i: integer; begin Result := nil; for i := 0 to FileCount - 1 do begin if GetFileName(FileByIndex(i)) = s then begin Result := FileByIndex(i); break; end; end; end; { FileByAuthor: Gets a file by an author. Example usage: f := FileByAuthor('rsalvatore'); } function FileByAuthor(s: string): IInterface; var i: integer; begin Result := nil; for i := 0 to FileCount - 1 do begin if GetAuthor(FileByIndex(i)) = s then begin Result := FileByIndex(i); break; end; end; end; { OverrideByFile: Example usage: ovr := OverrideByFile(record, aFile); if Assigned(ovr) then AddMessage('Found override for '+Name(record)+' in file '+GetFileName(aFile)); } function OverrideByFile(e, f: IInterface): IInterface; var i: Integer; ovr: IInterface; begin Result := nil; e := MasterOrSelf(e); for i := 0 to OverrideCount(e) - 1 do begin ovr := OverrideByIndex(e, i); if Equals(GetFile(ovr), f) then begin Result := ovr; break; end; end; end; { OverrideRecordCount: Gets the number of override records in a file or record group. Example usage: f := FileByName('Update.esm'); AddMessage(IntToStr(OverrideRecordCount(f))); } function OverrideRecordCount(f: IInterface): integer; var e: IInterface; i: Integer; begin Result := 0; if ElementTypeString(f) = 'etFile' then begin for i := 0 to Pred(RecordCount(f)) do begin e := RecordByIndex(f, i); if not Equals(MasterOrSelf(e), e) then Inc(Result); end; end else if ElementTypeString(f) = 'etGroupRecord' then begin for i := 0 to Pred(ElementCount(f)) do begin e := ebi(f, i); if ElementTypeString(e) = 'etGroupRecord' then Result := Result + OverrideRecordCount(e) else if not Equals(MasterOrSelf(e), e) then Inc(Result); end; end; end; { GetRecords: Add the records in a file or group to a stringlist. Example usage: slRecords := TStringList.Create; f := FileSelect('Select a file below:'); g := GroupBySignature(f, 'ARMO'); GetRecords(g, slRecords); } procedure GetRecords(g: IInterface; lst: TStringList); var r: IInterface; s: string; i: integer; begin for i := 0 to ElementCount(g) - 1 do begin r := ElementByIndex(g, i); if (Pos('GRUP Cell', Name(r)) = 1) then Continue; if (Pos('GRUP Exterior Cell', Name(r)) = 1) then begin ProcessElementsIn(r, lst); Continue; end; if (Signature(r) = 'GRUP') then ProcessElementsIn(r, lst) else if (Signature(r) = 'CELL') then lst.AddObject(Name(r), TObject(r)) else begin lst.AddObject(geev(r, 'EDID'), r); end; end; end; { GroupSignature: Gets the signature of a group record. This is useful if you want to get a list of the groups in a file. Example usage: s := GroupSignature(GroupBySignature(f, 'COBJ')); AddMessage(s); //'COBJ' } function GroupSignature(g: IInterface): string; var s: string; ct: integer; begin s := Name(g); ct := Length(s) - Length(Copy(s, 1, Pos('"', s))) - 1; Result := Copy(s, Pos('"', s) + 1, ct); end; { HexFormID Gets the formID of a record as a hexadecimal string. This is useful for just about every time you want to deal with FormIDs. Example usage: s := HexFormID(e); } function HexFormID(e: IInterface): string; var s: string; begin s := GetElementEditValues(e, 'Record Header\FormID'); if SameText(Signature(e), '') then Result := '00000000' else Result := Copy(s, Pos('[' + Signature(e) + ':', s) + Length(Signature(e)) + 2, 8); end; { FileFormID Gets the local File FormID of the record. Replaces the non-functional FixedFormID function. Example usage: c := FileFormID(e); } function FileFormID(e: IInterface): cardinal; begin Result := GetLoadOrderFormID(e) and $00FFFFFF; end; { IsLocalRecord Returns false for override and injected records. Example usage: e := RecordByIndex(f, 1); if IsLocalRecord(e) then AddMessage(Name(e) + ' is local.'); } function IsLocalRecord(e: IInterface): boolean; var loadOrder, pre: integer; loadFormID: string; begin loadOrder := GetLoadOrder(GetFile(e)); loadFormID := HexFormID(e); pre := StrToInt('$' + Copy(loadFormID, 1, 2)); Result := (pre >= loadOrder); end; { IsOverrideRecord Returns true for override records. Example usage: e := RecordByIndex(f, 1); if IsOverrideRecord(e) then AddMessage(Name(e) + ' is an override.'); } function IsOverrideRecord(e: IInterface): boolean; begin Result := not Equals(MasterOrSelf(e), e); end; { SmallName Gets the FormID and Editor ID of a record and outputs it as a string. This is nicer than Name for many records, as it doesn't produce a string that's a mile long. Example usage: s := SmallName(e); AddMessage(s); // outputs [ABCD:01234567] EditorID, ABCD being signature } function SmallName(e: IInterface): string; begin if signature(e) = 'REFR' then Result := '['+Signature(e)+':'+HexFormID(e)+'] '+GetElementEditValues(e, 'NAME') else Result := '['+Signature(e)+':'+HexFormID(e)+'] '+GetElementEditValues(e, 'EDID'); end; { ElementByIP: Element by Indexed Path This is a function to help with getting at elements that are inside lists. It allows you to use an "indexed path" to get at these elements that would otherwise be inaccessible without multiple lines of code. Example usage: element0 := ElementByIP(e, 'Conditions\[0]\CTDA - \Function'); element1 := ElementByIP(e, 'Conditions\[1]\CTDA - \Function'); } function ElementByIP(e: IInterface; ip: string): IInterface; var i, index: integer; path: TStringList; begin // replace forward slashes with backslashes ip := StringReplace(ip, '/', '\', [rfReplaceAll]); // prepare path stringlist delimited by backslashes path := TStringList.Create; path.Delimiter := '\'; path.StrictDelimiter := true; path.DelimitedText := ip; // traverse path for i := 0 to Pred(path.count) do begin if Pos('[', path[i]) > 0 then begin index := StrToInt(GetTextIn(path[i], '[', ']')); e := ElementByIndex(e, index); end else e := ElementByPath(e, path[i]); end; // set result Result := e; end; { ElementsByMIP This is a function that builds on ElementByIP by allowing the usage of the mult * character as a placeholder representing any valid index. It returns through @lst a list of all elements in @e that match the input path @ip. Example usage: lst := TList.Create; ElementsByMIP(lst, e, 'Items\[*]\CNTO - Item\Item'); for i := 0 to Pred(lst.Count) do begin AddMessage(GetEditValue(ObjectToElement(lst[i]))); end; lst.Free; } procedure ElementsByMIP(var lst: TList; e: IInterface; ip: string); var xstr: string; i, j, index: integer; path: TStringList; bMult: boolean; begin // replace forward slashes with backslashes ip := StringReplace(ip, '/', '\', [rfReplaceAll]); // prepare path stringlist delimited by backslashes path := TStringList.Create; path.Delimiter := '\'; path.StrictDelimiter := true; path.DelimitedText := ip; // traverse path bMult := false; for i := 0 to Pred(path.count) do begin if Pos('[', path[i]) > 0 then begin xstr := GetTextIn(path[i], '[', ']'); if xstr = '*' then begin for j := 0 to Pred(ElementCount(e)) do ElementsByMIP(lst, ElementByIndex(e, j), DelimitedTextBetween(path, i + 1, Pred(path.count))); bMult := true; break; end else e := ElementByIndex(e, index); end else e := ElementByPath(e, path[i]); end; if not bMult then lst.Add(TObject(e)); end; { IndexedPath: Gets the indexed path of an element. Example usage: element := ElementByIP(e, 'Conditions\[3]\CTDA - \Comparison Value'); AddMessage(IndexedPath(element)); //Conditions\[3]\CTDA - \Comparison Value } function IndexedPath(e: IInterface): string; var c: IInterface; a: string; begin c := GetContainer(e); while (ElementTypeString(e) <> 'etMainRecord') do begin if ElementTypeString(c) = 'etSubRecordArray' then a := '['+IntToStr(IndexOf(c, e))+']' else a := Name(e); if Result <> '' then Result := a + '\' + Result else Result := a; e := c; c := GetContainer(e); end; end; { ElementPath: Gets the path of an element. Example usage: element := ElementByPath(e, 'Model\MODL'); AddMessage(ElementPath(element)); //Model\MODL - Model Filename } function ElementPath(e: IInterface): string; var c: IInterface; begin c := GetContainer(e); while (ElementTypeString(e) <> 'etMainRecord') do begin if Result <> '' then Result := Name(e) + '\' + Result else Result := Name(e); e := c; c := GetContainer(e); end; end; { SetListEditValues: Sets the values of elements in a list to values stored in a stringlist. Example usage: SetListEditValues(e, 'Additional Races', slAdditionalRaces); } procedure SetListEditValues(e: IInterface; ip: string; values: TStringList); var i: integer; list, newelement: IInterface; begin // exit if values is empty if values.Count = 0 then exit; list := ElementByIP(e, ip); // clear element list except for one element While ElementCount(list) > 1 do RemoveByIndex(list, 0, true); // create elements and populate the list for i := 0 to values.Count - 1 do begin newelement := ElementAssign(list, HighInteger, nil, False); try SetEditValue(newelement, values[i]); except on Exception do Remove(newelement); // remove the invalid/failed element end; end; Remove(ElementByIndex(list, 0)); end; { SetListNativeValues: Sets the native values of elements in a list to the values stored in a Tlist. Example usage: SetListNativeValues(e, 'KWDA', lstKeywords); } procedure SetListNativeValues(e: IInterface; ip: string; values: TList); var i: integer; list, newelement: IInterface; begin // exit if values is empty if values.Count = 0 then exit; list := ElementByIP(e, ip); // clear element list except for one element While ElementCount(list) > 1 do RemoveByIndex(list, 0); // set element[0] to values[0] SetNativeValue(ElementByIndex(list, 0), values[0]); // create elements for the rest of the list for i := 1 to values.Count - 1 do begin newelement := ElementAssign(list, HighInteger, nil, False); SetNativeValue(newelement, values[i]); end; end; { ebn: ElementByName shortened function name. } function ebn(e: IInterface; n: string): IInterface; begin Result := ElementByName(e, n); end; { ebp: ElementByPath shortened function name. } function ebp(e: IInterface; p: string): IInterface; begin Result := ElementByPath(e, p); end; { ebi: ElementByIndex shortened function name. } function ebi(e: IInterface; i: integer): IInterface; begin Result := ElementByIndex(e, i); end; { ebip: ElementByIP shortened function name. } function ebip(e: IInterface; ip: string): IInterface; begin Result := ElementByIP(e, ip); end; { mgeev: Uses GetEditValues on each element in a list of elements to produce a stringlist of element edit values. Use with ElementsByMIP. Example usage: lst := TList.Create; // setup an arrray in lst with ElementsByMIP sl := TStringList.Create; mgeev(sl, lst); } procedure mgeev(var sl: TStringList; var lst: TList); var i: integer; e: IInterface; begin for i := 0 to Pred(lst.Count) do begin e := ObjectToElement(lst[i]); if Assigned(e) then sl.Add(GetEditValue(e)) else sl.Add(''); end; end; { geev: GetElementEditValues, enhanced with ElementByIP. Example usage: s1 := geev(e, 'Conditions\[3]\CTDA - \Function'); s2 := geev(e, 'KWDA\[2]'); } function geev(e: IInterface; ip: string): string; begin Result := GetEditValue(ElementByIP(e, ip)); end; { genv: GetElementNativeValues, enhanced with ElementByIP. Example usage: f1 := genv(e, 'KWDA\[3]'); f2 := genv(e, 'Armature\[2]'); } function genv(e: IInterface; ip: string): variant; begin Result := GetNativeValue(ElementByIP(e, ip)); end; { seev: SetElementEditValues, enhanced with ElementByIP. Example usage: seev(e, 'Conditions\[2]\CTDA - \Type', '10000000'); seev(e, 'KWDA\[0]'), } procedure seev(e: IInterface; ip: string; val: string); begin SetEditValue(ElementByIP(e, ip), val); end; { senv: SetElementNativeValues, enhanced with ElementByIP. Example usage: senv(e, 'KWDA\[1]', $0006C0EE); // $0006C0EE is ArmorHelmet keyword } procedure senv(e: IInterface; ip: string; val: variant); begin SetNativeValue(ElementByIP(e, ip), val); end; { slev: SetListEditValues shorted function name. Example usage: slev(e, 'Additional Races', slAdditionalRaces); } procedure slev(e: IInterface; ip: string; values: TStringList); begin SetListEditValues(e, ip, values); end; { slevo SetListEditValues - Objects. Sets the values in an array element to the objects in a TStringList. Example usage: slevo(e, 'KWDA', slKeywords); } procedure slevo(e: IInterface; ip: string; values: TStringList); var i: integer; list, newelement: IInterface; begin // exit if values is empty if values.Count = 0 then exit; list := ElementByIP(e, ip); // clear element list except for one element While ElementCount(list) > 1 do RemoveByIndex(list, 0); // set element[0] to values[0] SetEditValue(ElementByIndex(list, 0), Integer(values.Objects[0])); // create elements for the rest of the list for i := 1 to values.Count - 1 do begin newelement := ElementAssign(list, HighInteger, nil, False); SetEditValue(newelement, Integer(values.Objects[i])); end; end; { slnv: SetListNativeValues shorted function name. Example usage: slnv(e, 'KWDA', lstKeywords); } procedure slnv(e: IInterface; ip: string; values: TList); begin SetListNativeValues(e, ip, values); end; { gav: GetAllValues shortened function name. Example usage: gav(e); } function gav(e: IInterface): string; var i: integer; begin Result := GetEditValue(e); for i := 0 to ElementCount(e) - 1 do if (Result <> '') then Result := Result + ';' + gav(ElementByIndex(e, i)) else Result := gav(ElementByIndex(e, i)); end; { IsD: GetIsDeleted shortened function name. } function IsD(e: IInterface): boolean; begin Result := GetIsDeleted(e); end; { IsID: GetIsInitiallyDisabled shortened function name. } function IsID(e: IInterface): boolean; begin Result := GetIsInitiallyDisabled(e); end; { IsP: GetIsPersistent shortened function name. } function IsP(e: IInterface): boolean; begin Result := GetIsPersistent(e); end; { IsVWD: GetIsVisibleWhenDistant shortened function name. } function IsVWD(e: IInterface): boolean; begin Result := GetIsVisibleWhenDistant(e); end; { SetD: SetIsDeleted shortened function name. } procedure SetD(e: IInterface; b: boolean); begin SetIsDeleted(e, b); end; { SetID: SetIsInitiallyDisabled shortened function name. } procedure SetID(e: IInterface; b: boolean); begin SetIsInitiallyDisabled(e, b); end; { SetP: SetIsPersistent shortened function name. } procedure SetP(e: IInterface; b: boolean); begin SetIsPersistent(e, b); end; { SetVWD: SetIsVisibleWhenDistant shortened function name. } procedure SetVWD(e: IInterface; b: boolean); begin SetIsVisibleWhenDistant(e, b); end; { HasKeyword: Checks if an input record has a keyword matching the input EditorID. Example usage: if HasKeyword(e, 'ArmorHeavy') then AddMessage(Name(e) + ' is a heavy armor.'); } function HasKeyword(e: IInterface; edid: string): boolean; var kwda: IInterface; n: integer; begin Result := false; kwda := ElementByPath(e, 'KWDA'); for n := 0 to ElementCount(kwda) - 1 do if GetElementEditValues(LinksTo(ElementByIndex(kwda, n)), 'EDID') = edid then Result := true; end; { HasItem: Checks if an input record has an item matching the input EditorID. Example usage: if HasItem(e, 'IngotIron') then AddMessage(Name(e) + ' is made using iron!'); } function HasItem(rec: IInterface; s: string): boolean; var name: string; items, li: IInterface; i: integer; begin Result := false; items := ElementByPath(rec, 'Items'); if not Assigned(items) then exit; for i := 0 to ElementCount(items) - 1 do begin li := ElementByIndex(items, i); name := geev(LinksTo(ElementByPath(li, 'CNTO - Item\Item')), 'EDID'); if name = s then begin Result := true; Break; end; end; end; { HasPerkCondition: Checks if an input record has a HasPerk condition requiring a perk matching the input EditorID. Example usage: if HasPerkCondition(e, 'AdvancedSmithing') then AddMessage(Name(e) + ' is an advanced armor!'); } function HasPerkCondition(rec: IInterface; s: string): boolean; var name, func: string; conditions, ci: IInterface; i: integer; begin Result := false; conditions := ElementByPath(rec, 'Conditions'); if not Assigned(conditions) then exit; for i := 0 to ElementCount(conditions) - 1 do begin ci := ElementByIndex(conditions, i); func := geev(ci, 'CTDA - \Function'); if func = 'HasPerk' then begin name := geev(LinksTo(ElementByPath(ci, 'CTDA - \Perk')), 'EDID'); if name = s then begin Result := true; Break; end; end; end; end; { ExtractBSA: Extracts BSA matching aContainerName to aPath. Example usage: ExtractBSA(dataPath + 'Update.bsa', 'C:\TestExtract\'); } procedure ExtractBSA(aContainerName, aPath: string); var i: integer; slAssets: TStringList; begin // create directories ForceDirectories(aPath); // enumerate assets slAssets := TStringList.Create; ResourceList(aContainerName, slAssets); // save assets try for i := 0 to Pred(slAssets.Count) do begin //AddMessage(slAssets[i]); ResourceCopy(aContainerName, slAssets[i], aPath); end; except on E: Exception do AddMessage('Error copying file ' + slAssets[i] + ': ' + E.Message); end; // free stringlists slAssets.Free; end; { ExtractPathBSA: Extracts assets from a BSA that match a specified path. Example usage: ExtractPathBSA(DataPath + 'SkyUI.esp', TempPath, 'interface\translations'); } procedure ExtractPathBSA(aContainerName, aPath, aSubPath: string); var i: integer; slAssets: TStringList; begin // create directories ForceDirectories(aPath); // enumerate assets slAssets := TStringList.Create; ResourceList(aContainerName, slAssets); // save assets try for i := 0 to Pred(slAssets.Count) do begin //AddMessage(slAssets[i]); if Pos(Lowercase(aSubPath), LowerCase(slAssets[i])) = 1 then ResourceCopy(aContainerName, slAssets[i], aPath); end; except on E: Exception do AddMessage('Error copying file ' + slAssets[i] + ': ' + E.Message); end; // free stringlists slAssets.Free; end; { PrintBSAContents: Prints to the log the contents of a BSA file. Example usage: PrintBSAContents(dataPath + 'Update.bsa'); } procedure PrintBSAContents(aContainerName); var i: integer; slAssets: TStringList; begin // enumerate assets slAssets := TStringList.Create; ResourceList(aContainerName, slAssets); // print assets for i := 0 to Pred(slAssets.Count) do AddMessage(slAssets[i]); // free stringlist slAssets.Free; end; { AddMastersToList: Adds the masters from a specific file, and the file itself, to a specified stringlist. Example usage: slMasters := TStringList.Create; AddMastersToList(FileByName('Dragonborn.esm'), slMasters); } procedure AddMastersToList(f: IInterface; var lst: TStringList); var masters, master: IInterface; i: integer; s: string; begin // add file s := GetFileName(f); if (lst.IndexOf(s) = -1) then lst.Add(s); // add file's masters masters := ElementByPath(ElementByIndex(f, 0), 'Master Files'); if Assigned(masters) then begin for i := 0 to ElementCount(masters) - 1 do begin s := geev(ElementByIndex(masters, i), 'MAST'); if (lst.IndexOf(s) = -1) then lst.Add(s); end; end; end; { AddMastersToFile: Adds masters from a stringlist to the specified file. Example usage: slMasters := TStringList.Create; slMasters.Add('Skyrim.esm'); slMasters.Add('Update.esm'); UserFile := FileSelect('Select the file you wish to use below: '); AddMastersToFile(UserFile, slMasters, False); } procedure AddMastersToFile(f: IInterface; lst: TStringList; silent: boolean); var masters, master: IInterface; i: integer; s: string; slCurrentMasters: TStringList; begin // create local stringlist slCurrentMasters := TStringList.Create; // AddMasterIfMissing will attempt to add the masters to the file. if not silent then AddMessage(' Adding masters to '+GetFileName(f)+'...'); for i := 0 to lst.Count - 1 do begin if (Lowercase(lst[i]) <> Lowercase(GetFileName(f))) then AddMasterIfMissing(f, lst[i]); end; // AddMasterIfMissing won't add the masters if they have been removed // in the current TES5Edit session, so a manual re-adding process is // used. This process can't fully replace AddMasterIfMissing without // causing problems. It only works for masters that have been removed // in the current TES5Edit session. masters := ElementByPath(ElementByIndex(f, 0), 'Master Files'); if not Assigned(masters) then begin Add(ElementByIndex(f, 0), 'Master Files'); masters := ElementByPath(ElementByIndex(f, 0), 'Master Files'); end; for i := 0 to ElementCount(masters) - 1 do begin s := geev(ElementByIndex(masters, i), 'MAST'); slCurrentMasters.Add(s); end; for i := 0 to lst.Count - 1 do begin if (Lowercase(lst[i]) <> Lowercase(GetFileName(f))) and (slCurrentMasters.IndexOf(lst[i]) = -1) then begin master := ElementAssign(masters, HighInteger, nil, False); SetElementEditValues(master, 'MAST', lst[i]); AddMessage(' +Re-added master: '+lst[i]); end; end; // free stringlist slCurrentMasters.Free; end; { RemoveMaster: Removes a master matching the specified string from the specified file. Example usage: f := FileByIndex(i); RemoveMaster(f, 'Update.esm'); } procedure RemoveMaster(f: IInterface; mast: String); var masters: IInterface; i: integer; s: string; begin masters := ElementByPath(ElementByIndex(f, 0), 'Master Files'); for i := ElementCount(masters) - 1 downto 0 do begin s := geev(ElementByIndex(masters, i), 'MAST'); if s = mast then begin Remove(ElementByIndex(masters, i)); break; end; end; end; { FileSelect: Creates a form for the user to select a file to be used. Example usage: UserFile := FileSelect('Select the file you wish to use below: '); } function FileSelect(prompt: string): IInterface; var frm: TForm; lbl: TLabel; cbFiles: TComboBox; btnOk, btnCancel: TButton; i: integer; s: string; begin frm := TForm.Create(nil); try frm.Caption := 'Select File'; frm.Width := 300; frm.Height := 170; frm.Position := poScreenCenter; lbl := TLabel.Create(frm); lbl.Parent := frm; lbl.Width := 284; if Pos(#13, prompt) > 0 then begin lbl.Height := 60; end else begin lbl.Height := 30; frm.Height := 160; end; lbl.Left := 8; lbl.Top := 8; lbl.Caption := prompt; lbl.Autosize := false; lbl.Wordwrap := True; cbFiles := TComboBox.Create(frm); cbFiles.Parent := frm; cbFiles.Style := csDropDownList; cbFiles.Items.Add('-- CREATE NEW FILE --'); cbFiles.Top := lbl.Top + lbl.Height + 20; cbFiles.Left := 8; cbFiles.Width := 200; for i := 0 to FileCount - 1 do begin s := GetFileName(FileByIndex(i)); if (Pos(s, bethesdaFiles) > 0) then Continue; cbFiles.Items.Add(s); end; cbFiles.ItemIndex := 0; btnOk := TButton.Create(frm); btnOk.Parent := frm; btnOk.Left := 150 - btnOk.Width - 8; btnOk.Top := cbFiles.Top + 40; btnOk.Caption := 'OK'; btnOk.ModalResult := mrOk; btnCancel := TButton.Create(frm); btnCancel.Parent := frm; btnCancel.Caption := 'Cancel'; btnCancel.ModalResult := mrCancel; btnCancel.Left := btnOk.Left + btnOk.Width + 16; btnCancel.Top := btnOk.Top; if frm.ShowModal = mrOk then begin if (cbFiles.Text = '-- CREATE NEW FILE --') then Result := AddNewFile else begin for i := 0 to FileCount - 1 do begin if (cbFiles.Text = GetFileName(FileByIndex(i))) then begin Result := FileByIndex(i); Break; end; if i = FileCount - 1 then begin AddMessage('The script couldn''t find the file you entered.'); Result := FileSelect(prompt); end; end; end; end; finally frm.Free; end; end; { MultiFileSelect: Gives the user a dialog from which they can select one or more files. Example usage: files := TStringList.Create; MultiFileSelect(files, 'Select some files'); AddMesage(files.Text); // The files the user selected files.Free; } procedure MultiFileSelect(var sl: TStringList; prompt: string); const spacing = 24; var frm: TForm; pnl: TPanel; lastTop, contentHeight: Integer; cbArray: Array[0..255] of TCheckBox; lbl: TLabel; sb: TScrollBox; i: Integer; f: IInterface; begin frm := TForm.Create(nil); try frm.Position := poScreenCenter; frm.Width := 300; frm.Height := 600; frm.BorderStyle := bsDialog; frm.Caption := 'Multiple file selection'; // create scrollbox sb := TScrollBox.Create(frm); sb.Parent := frm; sb.Align := alTop; sb.Height := 500; // create label lbl := TLabel.Create(sb); lbl.Parent := sb; lbl.Caption := prompt; lbl.Left := 8; lbl.Top := 8; lbl.Width := 280; lbl.WordWrap := true; lastTop := lbl.Top + lbl.Height + 8 - spacing; // create checkboxes for i := 0 to FileCount - 2 do begin f := FileByLoadOrder(i); cbArray[i] := TCheckBox.Create(sb); cbArray[i].Parent := sb; cbArray[i].Caption := Format(' [%s] %s', [IntToHex(i, 2), GetFileName(f)]); cbArray[i].Top := lastTop + spacing; cbArray[i].Width := 260; lastTop := lastTop + spacing; cbArray[i].Left := 12; cbArray[i].Checked := sl.IndexOf(GetFileName(f)) > -1; end; contentHeight := spacing*(i + 2) + 100; if frm.Height > contentHeight then frm.Height := contentHeight; // create modal buttons cModal(frm, frm, frm.Height - 70); sl.Clear; if frm.ShowModal = mrOk then begin for i := 0 to FileCount - 2 do begin f := FileByLoadOrder(i); if (cbArray[i].Checked) and (sl.IndexOf(GetFileName(f)) = -1) then sl.Add(GetFileName(f)); end; end; finally frm.Free; end; end; { RecordSelect: Gives the user a dialog from which they can select a record. You can use this window four different ways: - Inputting nil for both arguments. This will allow the user to select a file, a record group, and a record. - Inputting a file. This will allow the user to select a record group and a record. - Inputting a record group. This will allow the user to select a file and a record. - Inputting a file and a record group. This will allow the user to only select a record. Example usage: aRecord := RecordSelect('', ''); aRecord := RecordSelect('Skyrim.esm', ''); aRecord := RecordSelect('', 'ARMO'); aRecord := RecordSelect('Skyrim.esm', 'ARMO'); } procedure rsLoadRecords(Sender: TObject); var f, g: IInterface; fn: string; i, j: integer; pnl: TPanel; begin pnl := TComboBox(Sender).GetParentComponent; // clear records and exit if invalid group specified if TComboBox(Sender).ItemIndex = -1 then TComboBox(pnl.Controls[2]).Items.Clear else begin // find file fn := pnl.Controls[0].Text; f := FileByName(fn); // if file found, set records combobox content if Assigned(f) then begin g := GroupBySignature(f, TComboBox(Sender).Text); GetRecords(g, TComboBox(pnl.Controls[2]).Items); TComboBox(pnl.Controls[2]).Text := ''; end; end; end; procedure rsLoadGroups(Sender: TObject); var fn, sGroups: string; f, g: IInterface; i: integer; pnl: TPanel; begin pnl := TComboBox(Sender).GetParentComponent; // clear groups, records, and exit if invalid file specified if TComboBox(Sender).ItemIndex = -1 then begin TComboBox(pnl.Controls[1]).Items.Clear; TComboBox(pnl.Controls[2]).Items.Clear; end // load records if selecting groups is disabled else if not TComboBox(pnl.Controls[1]).Enabled then rsLoadRecords(pnl.Controls[1]) else begin // find file fn := TComboBox(Sender).Text; f := FileByName(fn); // if file found, load groups if Assigned(f) then begin sGroups := ''; for i := 0 to ElementCount(f) - 1 do begin g := ElementByIndex(f, i); if Signature(g) = 'TES4' then Continue; if not (sGroups = '') then sGroups := sGroups + #13 + GroupSignature(g) else sGroups := GroupSignature(g); end; TComboBox(pnl.Controls[1]).Items.Text := sGroups; TComboBox(pnl.Controls[1]).Text := ''; end; end; end; function RecordSelect(sFile, sGroup: string): IInterface; var frm: TForm; lbl: TLabel; cb1, cb2, cb3: TComboBox; i: integer; pnl: TPanel; e: IInterface; sFileList, prompt: string; begin // set up prompt caption if (sFile <> '') then begin if (sGroup <> '') then prompt := 'Choose a record:' else prompt := 'Choose a group, then choose a record:'; end else begin if (sGroup <> '') then prompt := 'Choose a file, then choose a record:' else prompt := 'Choose a file, a group, and a record:'; end; // prepare sFileList for i := 0 to FileCount - 1 do begin if not (sFileList = '') then sFileList := sFileList + #13 + GetFileName(FileByLoadOrder(i)) else sFileList := GetFileName(FileByLoadOrder(i)); end; // display form frm := TForm.Create(nil); try frm.Caption := 'Choose a record'; frm.Width := 380; frm.Height := 140; frm.Position := poScreenCenter; frm.BorderStyle := bsDialog; // create label instructing user what to do lbl := TLabel.Create(frm); lbl.Parent := frm; lbl.Left := 8; lbl.Top := 8; lbl.Width := frm.Width - 16; lbl.Caption := prompt; // create panel to hold comboboxes pnl := TPanel.Create(frm); pnl.Parent := frm; pnl.Left := 8; pnl.Top := 32; pnl.Width := frm.Width - 16; pnl.Height := 30; pnl.BevelOuter := bvNone; // create Files combobox cb1 := TComboBox.Create(frm); cb1.Parent := pnl; cb1.Left := 0; cb1.Top := 0; cb1.Width := 100; cb1.Autocomplete := True; cb1.Style := csDropDown; cb1.Sorted := False; cb1.AutoDropDown := True; cb1.Items.Text := sFileList; cb1.Text := ''; cb1.OnSelect := rsLoadGroups; // create groups combobox cb2 := TComboBox.Create(frm); cb2.Parent := pnl; cb2.Left := cb1.Left + cb1.Width + 8; cb2.Top := cb1.Top; cb2.Width := 70; cb2.Autocomplete := True; cb2.Style := csDropDown; cb2.Sorted := True; cb2.AutoDropDown := True; cb2.Text := ''; cb2.OnSelect := rsLoadRecords; // create records combobox cb3 := TComboBox.Create(frm); cb3.Parent := pnl; cb3.Left := cb2.Left + cb2.Width + 8; cb3.Top := cb1.Top; cb3.Width := 149; cb3.Autocomplete := True; cb3.Style := csDropDown; cb3.Sorted := True; cb3.AutoDropDown := True; cb3.Text := ''; // construct ok and cancel buttons cModal(frm, frm, 70); // set up form based on input variables if cb1.Items.IndexOf(sFile) > -1 then begin cb1.Enabled := false; cb1.ItemIndex := cb1.Items.IndexOf(sFile); rsLoadGroups(cb1); end; if sGroup <> '' then begin cb2.Enabled := false; cb2.Items.Add(sGroup); cb2.ItemIndex := cb2.Items.IndexOf(sGroup); if sFile <> '' then rsLoadRecords(cb2); end; if frm.ShowModal = mrOk then if cb3.ItemIndex > -1 then Result := ObjectToElement(cb3.Items.Objects[cb3.Items.IndexOf(cb3.Text)]); finally frm.Free; end; end; { EditOutOfDate: Informs the user that their xEdit is out of date, and provides an option to open the AFKMods thread to download the current beta. Example usage: EditOutOfDate('3.0.33 svn 1898', 'http://afkmods.iguanadons.net/index.php?/topic/3750-wipz-tes5edit/'); } procedure EditOutOfDate(minimumVersion: String; url: string); var frm: TForm; lbl: TLabel; btnOk, btnCancel: TButton; v: integer; s: string; begin frm := TForm.Create(nil); try frm.Caption := 'xEdit out of Date!'; frm.Width := 300; frm.Height := 150; frm.Position := poScreenCenter; try v := wbVersionNumber; s := Format('%sEdit version %d.%d.%d', [ wbAppName, v shr 24, v shr 16 and $FF, v shr 8 and $FF ]); except on Exception do s := wbAppName + 'Edit 3.0.31 or earlier'; end; lbl := TLabel.Create(frm); lbl.Parent := frm; lbl.Top := 8; lbl.Left := 8; lbl.WordWrap := True; lbl.Width := 270; lbl.Caption := 'You''re using '+s+', but this script requires '+wbAppName+'Edit '+minimumVersion+' or newer. ' 'Click the Update button to be directed to get the latest version.'; AddMessage('You''re using '+s+', but this script requires '+wbAppName+'Edit '+minimumVersion+' or newer.'); AddMessage('You can get the latest version at '+url); btnOk := TButton.Create(frm); btnOk.Parent := frm; btnOk.Top := lbl.Top + lbl.Height + 16; btnOk.Left := 40; btnOk.Caption := 'Update'; btnOk.ModalResult := mrOk; btnOk.Hint := 'Click to open '+url+' in '#13#10+ 'your internet browser so you can download the latest xEdit beta version.'; btnOk.ShowHint := true; btnCancel := TButton.Create(frm); btnCancel.Parent := frm; btnCancel.Top := btnOk.Top; btnCancel.Left := btnOk.Left + btnOk.Width + 20; btnCancel.Caption := 'Cancel'; btnCancel.ModalResult := mrCancel; frm.Height := btnOk.Top + btnOk.Height + 50; if frm.ShowModal = mrOk then begin ShellExecute(TForm(frm).Handle, 'open', url, '', '', SW_SHOWNORMAL); end; finally frm.Free; end; end; { BoolToChecked: A function which returns the TCheckBoxState corresponding to a boolean value. Example usage: cb.State := BoolToCheck(true); } function BoolToChecked(b: boolean): TCheckBoxState; begin if b then Result := cbChecked else Result := cbUnchecked; end; { CheckedToBool: A function which returns a boolean corresponding to a TCheckBoxState. Example usage: if CheckedToBool(cb.State) then AddMessage('Hi!'); } function CheckedToBool(cbs: TCheckBoxState): boolean; begin Result := (cbs = cbChecked); end; { ConstructGroup: A function which can be used to make a GroupBox. Used to make code more compact. Example usage: group := ConstructGroup(frm, frm, 8, 8, 300, 300, 'My Group'); } function ConstructGroup(h, p: TObject; top, left, height, width: Integer; caption, t: string): TGroupBox; var gb: TGroupBox; begin gb := TGroupBox.Create(h); gb.Parent := p; gb.Top := top; gb.Left := left; gb.Width := width; gb.Height := height; gb.ClientWidth := width - 15; gb.ClientHeight := height - 15; gb.Caption := caption; if (t <> '') then begin gb.ShowHint := true; gb.Hint := t; end; Result := gb; end; { cGroup: Shortened version of ConstructGroup Example usage: group := cGroup(frm, frm, 8, 8, 300, 300, 'My Group'); } function cGroup(h, p: TObject; top, left, height, width: Integer; caption, t: string): TGroupBox; begin Result := ConstructGroup(h, p, top, left, height, width, caption, t); end; { ConstructImage: A function which can be used to make an image. Used to make code more compact. Example usage: img := ConstructImage(frm, frm, 8, 8, 300, 300, gear, 'Options'); } function ConstructImage(h, p: TObject; top, left, height, width: Integer; picture: TPicture; t: string): TImage; var img: TImage; begin img := TImage.Create(h); img.Parent := p; img.Top := top; img.Left := left; img.Width := width; img.Height := height; img.Picture := picture; if (t <> '') then begin img.ShowHint := true; img.Hint := t; end; Result := img; end; { cImage: Shortened version of ConstructImage Example usage: img := cImage(frm, frm, 8, 8, 300, 300, gear, 'Options'); } function cImage(h, p: TObject; top, left, height, width: Integer; picture: TPicture; t: String): TImage; begin Result := ConstructImage(h, p, top, left, height, width, picture, t); end; { ConstructRadioGroup: A function which can be used to make a radio group. Used to make code more compact. Example usage: rg := ConstructRadioGroup(frm, frm, 8, 8, 200, 400, 'Options'); } function ConstructRadioGroup(h, p: TObject; top, left, height, width: Integer; text: String): TRadioGroup; var rg: TRadioGroup; begin rg := TRadioGroup.Create(h); rg.Parent := p; rg.Top := top; rg.Left := left; rg.Width := width; rg.Height := height; rg.Caption := text; rg.ClientHeight := height - 15; rg.ClientWidth := width - 15; Result := rg; end; { cRadioGroup: Shortened version of ConstructRadioGroup. Example usage: rg := cRadioGroup(frm, frm, 8, 8, 200, 400, 'Options'); } function cRadioGroup(h, p: TObject; top, left, height, width: Integer; text: String): TRadioGroup; begin Result := ConstructRadioGroup(h, p, top, left, height, width, text); end; { ConstructRadioButton: A function which can be used to make a radio button. Used to make code more compact. Example usage: rb := ConstructRadioButton(frm, frm, 8, 8, 200, 400, 'This way', false); } function ConstructRadioButton(h, p: TObject; top, left, height, width: Integer; text: String; checked: boolean): TRadioButton; var rb: TRadioButton; begin rb := TRadioButton.Create(h); rb.Parent := p; rb.Top := top; rb.Left := left; if width > 0 then rb.Width := width; if height > 0 then rb.Height := height; rb.Caption := text; rb.Checked := checked; Result := rb; end; { cRadioButton: Shortened version of ConstructRadioButton. Example usage: rg := cRadioButton(frm, frm, 8, 8, 200, 400, 'This way', false); } function cRadioButton(h, p: TObject; top, left, height, width: Integer; text: String; checked: boolean): TRadioButton; begin Result := ConstructRadioButton(h, p, top, left, height, width, text, checked); end; { ConstructMemo: A function which can be used to make a memo. Used to make code more compact. Example usages: memo := ConstructMemo(frm, frm, 0, 0, 200, 400, True, True, ssBoth, ''); } function ConstructMemo(h, p: TObject; top, left, height, width: Integer; ww, ro: boolean; ss: TScrollStyle; text: String): TMemo; var memo: TMemo; begin memo := TMemo.Create(h); memo.Parent := p; memo.Top := top; memo.Left := left; if width > 0 then memo.Width := width; if height > 0 then memo.Height := height; memo.WordWrap := ww; memo.ReadOnly := ro; memo.ScrollBars := ss; memo.Text := text; Result := memo; end; { cMemo: Shortened function name for ConstructMemo. Example usages: memo := ConstructMemo(frm, frm, 0, 0, 200, 400, True, True, ssBoth, ''); } function cMemo(h, p: TObject; top, left, height, width: Integer; ww, ro: boolean; ss: TScrollStyle; text: String): TMemo; begin Result := ConstructMemo(h, p, top, left, height, width, ww, ro, ss, text); end; { ConstructScrollBox: A function which can be used to make a scrollbox. Used to make code more compact. Example usage: sb := ConstructScrollBox(frm, frm, 400, alTop); } function ConstructScrollBox(h, p: TObject; height: Integer; a: TAlign): TScrollBox; var sb: TScrollBox; begin sb := TScrollBox.Create(h); sb.Parent := p; sb.Height := height; sb.Align := a; Result := sb; end; { cScrollBox: Shortened function name for ConstructScrollBox. Example usage: sb := cScrollBox(frm, frm, 400, alTop); } function cScrollBox(h, p: TObject; height: Integer; a: TAlign): TScrollBox; begin Result := ConstructScrollBox(h, p, height, a); end; { ConstructCheckbox: A function which can be used to make a checkbox. Used to make code more compact. Example usage: cb1 := ConstructCheckBox(frm, pnlBottom, 8, 8, 160, 'Remove persistent references', cbChecked); } function ConstructCheckBox(h, p: TObject; top, left, width: Integer; s: String; state: TCheckBoxState; t: string): TCheckBox; var cb: TCheckBox; begin cb := TCheckBox.Create(h); cb.Parent := p; cb.Top := top; cb.Left := left; cb.Width := width; cb.Caption := s; cb.State := state; if (t <> '') then begin cb.ShowHint := true; cb.Hint := t; end; Result := cb; end; { cCheckBox: Shortened function name for ConstructCheckBox. Example usage: cb1 := cCheckBox(frm, pnlBottom, 8, 8, 160, 'Remove persistent references', cbChecked); } function cCheckBox(h, p: TObject; top, left, width: Integer; s: String; state: TCheckBoxState; t: string): TCheckBox; begin Result := ConstructCheckBox(h, p, top, left, width, s, state, t); end; { ConstructLabel: A function which can be used to make a label. Used to make code more compact. Example usage: lbl3 := ConstructLabel(frm, pnlBottom, 65, 8, 0, 0, 'Reference removal options:'); } function ConstructLabel(h, p: TObject; top, left, height, width: Integer; s, t: String): TLabel; var lb: TLabel; begin lb := TLabel.Create(h); lb.Parent := p; lb.Top := top; lb.Left := left; lb.AutoSize := false; lb.WordWrap := true; if (height = 0) and (width = 0) then lb.AutoSize := true; if (height = 0) and (Pos(#13, s) > 0) then lb.AutoSize := true; if height > 0 then lb.Height := height; if width > 0 then lb.Width := width; lb.Caption := s; if (t <> '') then begin lb.ShowHint := true; lb.Hint := t; end; Result := lb; end; { cLabel: Shortened function name for ConstructLabel. Example usage: lbl3 := cLabel(frm, pnlBottom, 65, 8, 0, 0, 'Reference removal options:'); } function cLabel(h, p: TObject; top, left, height, width: Integer; s, t: String): TLabel; begin Result := ConstructLabel(h, p, top, left, height, width, s, t); end; { ConstructEdit: A function which can be used to make an edit field. Used to make code more compact. Example usage: ed3 := ConstructEdit(frm, frm, 100, 8, 0, 0, 'Edit me!'); } function ConstructEdit(h, p: TObject; top, left, height, width: Integer; s, t: String): TLabel; var ed: TLabel; begin ed := TEdit.Create(h); ed.Parent := p; ed.Top := top; ed.Left := left; if height > 0 then ed.Height := height; if width > 0 then ed.Width := width; if (height = 0) and (width = 0) then ed.AutoSize := true; ed.Text := s; if (t <> '') then begin ed.ShowHint := true; ed.Hint := t; end; Result := ed; end; { cEdit: Shortened function name for ConstructEdit. Example usage: ed3 := cEdit(frm, frm, 100, 8, 0, 0, 'Edit me!'); } function cEdit(h, p: TObject; top, left, height, width: Integer; s, t: String): TLabel; begin Result := ConstructEdit(h, p, top, left, height, width, s, t); end; { ConstructButton: A function which can be used to make a button. Used to make code more compact. Example usage: btn1 := ConstructButton(frm, pnlBottom, 8, 8, 160, 'OK'); } function ConstructButton(h, p: TObject; top, left, height, width: Integer; s: String): TButton; var btn: TButton; begin btn := TButton.Create(h); btn.Parent := p; btn.Top := top; btn.Left := left; if height > 0 then btn.Height := height; if width > 0 then btn.Width := width; btn.Caption := s; Result := btn; end; { cButton: Shortened function name for ConstructButton. Example usage: cButton(frm, pnlBottom, 8, 8, 160, 'OK'); } function cButton(h, p: TObject; top, left, height, width: Integer; s: String): TButton; begin Result := ConstructButton(h, p, top, left, height, width, s); end; { ConstructLabelEditPair: A function which makes a TLabel TEdit pair in a container and returns a reference to the edit. Example usage: edPosition := ConstructLabelEditPair(frm, 8, 8, 150, 'Minimum position offset:', '0.5'); } function ConstructLabelEditPair(c: TObject; top, left, spacing, edw: Integer; caption, text, t: string): TEdit; var lbl: TLabel; ed: TEdit; begin lbl := cLabel(c, c, top, left, 0, spacing, caption, t); ed := cEdit(c, c, top, left + spacing, 0, edw, text, t); Result := ed; end; { cPair: Shortened function name for ConstructLabelEditPair. Example usage: edPosition := cPair(frm, 8, 8, 150, 'Minimum position offset:', '0.5'); } function cPair(c: TObject; top, left, spacing, edw: Integer; caption, text, t: string): TEdit; begin Result := ConstructLabelEditPair(c, top, left, spacing, edw, caption, text, t); end; { ConstructModalButtons: A procedure which makes the standard OK and Cancel buttons on a form. Example usage: ConstructModalButtons(frm, pnlBottom, frm.Height - 80); } procedure ConstructModalButtons(h, p: TObject; top: Integer); var btnOk: TButton; btnCancel: TButton; begin btnOk := TButton.Create(h); btnOk.Parent := p; btnOk.Caption := 'OK'; btnOk.ModalResult := mrOk; btnOk.Left := h.Width div 2 - btnOk.Width - 8; btnOk.Top := top; btnCancel := TButton.Create(h); btnCancel.Parent := p; btnCancel.Caption := 'Cancel'; btnCancel.ModalResult := mrCancel; btnCancel.Left := btnOk.Left + btnOk.Width + 16; btnCancel.Top := btnOk.Top; end; { cModal: Shortened function name for ConstructModalButtons. } procedure cModal(h, p: TObject; top: Integer); begin ConstructModalButtons(h, p, top); end; end.