From 98597e1a0dbaff14db7cec72b419d2696623bead Mon Sep 17 00:00:00 2001 From: Bergmann89 Date: Sun, 21 Sep 2014 19:44:26 +0200 Subject: [PATCH] * initial commit --- Tests/UtilsTests.ico | Bin 0 -> 137040 bytes Tests/UtilsTests.lpi | 84 ++ Tests/UtilsTests.lpr | 23 + Tests/UtilsTests.lps | 170 +++ Tests/UtilsTests.res | Bin 0 -> 138128 bytes Tests/uGenericsTests.pas | 710 +++++++++++ uutlCommon.pas | 394 ++++++ uutlConsoleHelper.pas | 2128 +++++++++++++++++++++++++++++++++ uutlConversion.pas | 66 + uutlEmbeddedProfiler.inc | 45 + uutlEmbeddedProfiler.pas | 301 +++++ uutlEnumHelper.inc | 58 + uutlEnumHelper.pas | 101 ++ uutlEventManager.pas | 712 +++++++++++ uutlExceptions.pas | 103 ++ uutlGenerics.pas | 1413 ++++++++++++++++++++++ uutlGraph.pas | 413 +++++++ uutlKeyCodes.pas | 263 ++++ uutlLocalization.pas | 244 ++++ uutlLogger.pas | 436 +++++++ uutlMCF.pas | 645 ++++++++++ uutlMcfHelper.pas | 100 ++ uutlMessageThread.pas | 453 +++++++ uutlMessages.pas | 201 ++++ uutlPlatform.pas | 93 ++ uutlProfilerBinary.inc | 63 + uutlProfilerPlainText.inc | 49 + uutlProfilerPlainTextMMap.inc | 46 + uutlSetHelper.inc | 71 ++ uutlSettings.pas | 371 ++++++ uutlStreamHelper.pas | 673 +++++++++++ uutlSystemInfo.pas | 523 ++++++++ uutlTiming.pas | 84 ++ 33 files changed, 11036 insertions(+) create mode 100644 Tests/UtilsTests.ico create mode 100644 Tests/UtilsTests.lpi create mode 100644 Tests/UtilsTests.lpr create mode 100644 Tests/UtilsTests.lps create mode 100644 Tests/UtilsTests.res create mode 100644 Tests/uGenericsTests.pas create mode 100644 uutlCommon.pas create mode 100644 uutlConsoleHelper.pas create mode 100644 uutlConversion.pas create mode 100644 uutlEmbeddedProfiler.inc create mode 100644 uutlEmbeddedProfiler.pas create mode 100644 uutlEnumHelper.inc create mode 100644 uutlEnumHelper.pas create mode 100644 uutlEventManager.pas create mode 100644 uutlExceptions.pas create mode 100644 uutlGenerics.pas create mode 100644 uutlGraph.pas create mode 100644 uutlKeyCodes.pas create mode 100644 uutlLocalization.pas create mode 100644 uutlLogger.pas create mode 100644 uutlMCF.pas create mode 100644 uutlMcfHelper.pas create mode 100644 uutlMessageThread.pas create mode 100644 uutlMessages.pas create mode 100644 uutlPlatform.pas create mode 100644 uutlProfilerBinary.inc create mode 100644 uutlProfilerPlainText.inc create mode 100644 uutlProfilerPlainTextMMap.inc create mode 100644 uutlSetHelper.inc create mode 100644 uutlSettings.pas create mode 100644 uutlStreamHelper.pas create mode 100644 uutlSystemInfo.pas create mode 100644 uutlTiming.pas diff --git a/Tests/UtilsTests.ico b/Tests/UtilsTests.ico new file mode 100644 index 0000000000000000000000000000000000000000..0341321b5d952e1662a3d9444a73cf9f42a7db37 GIT binary patch literal 137040 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 G`+orYGy**U literal 0 HcmV?d00001 diff --git a/Tests/UtilsTests.lpi b/Tests/UtilsTests.lpi new file mode 100644 index 0000000..03fa37e --- /dev/null +++ b/Tests/UtilsTests.lpi @@ -0,0 +1,84 @@ + + + + + + + + + + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <Icon Value="0"/> + </General> + <i18n> + <EnableI18N LFM="False"/> + </i18n> + <VersionInfo> + <StringTable ProductVersion=""/> + </VersionInfo> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + </PublishOptions> + <RunParams> + <local> + <FormatVersion Value="1"/> + </local> + </RunParams> + <RequiredPackages Count="3"> + <Item1> + <PackageName Value="FPCUnitTestRunner"/> + </Item1> + <Item2> + <PackageName Value="LCL"/> + </Item2> + <Item3> + <PackageName Value="FCL"/> + </Item3> + </RequiredPackages> + <Units Count="2"> + <Unit0> + <Filename Value="UtilsTests.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="uGenericsTests.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="uGenericsTests"/> + </Unit1> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="UtilsTests"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <OtherUnitFiles Value=".."/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Debugging> + <UseHeaptrc Value="True"/> + </Debugging> + </Linking> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/Tests/UtilsTests.lpr b/Tests/UtilsTests.lpr new file mode 100644 index 0000000..a51e236 --- /dev/null +++ b/Tests/UtilsTests.lpr @@ -0,0 +1,23 @@ +program UtilsTests; + +{$mode objfpc}{$H+} + +uses + sysutils, Interfaces, Forms, GuiTestRunner, uGenericsTests; + +{$R *.res} + +var + heaptrcFile: String; + +begin + heaptrcFile := ChangeFileExt(Application.ExeName, '.heaptrc'); + if (FileExists(heaptrcFile)) then + DeleteFile(heaptrcFile); + SetHeapTraceOutput(heaptrcFile); + + Application.Initialize; + Application.CreateForm(TGuiTestRunner, TestRunner); + Application.Run; +end. + diff --git a/Tests/UtilsTests.lps b/Tests/UtilsTests.lps new file mode 100644 index 0000000..4dfc195 --- /dev/null +++ b/Tests/UtilsTests.lps @@ -0,0 +1,170 @@ +<?xml version="1.0" encoding="UTF-8"?> +<CONFIG> + <ProjectSession> + <PathDelim Value="\"/> + <Version Value="9"/> + <BuildModes Active="Default"/> + <Units Count="5"> + <Unit0> + <Filename Value="UtilsTests.lpr"/> + <IsPartOfProject Value="True"/> + <EditorIndex Value="2"/> + <CursorPos X="12" Y="6"/> + <UsageCount Value="22"/> + <Loaded Value="True"/> + </Unit0> + <Unit1> + <Filename Value="uGenericsTests.pas"/> + <IsPartOfProject Value="True"/> + <UnitName Value="uGenericsTests"/> + <IsVisibleTab Value="True"/> + <TopLine Value="559"/> + <CursorPos X="54" Y="580"/> + <UsageCount Value="22"/> + <Loaded Value="True"/> + </Unit1> + <Unit2> + <Filename Value="C:\Zusatzprogramme\Lazarus\fpc\2.7.1\source\packages\fcl-fpcunit\src\fpcunit.pp"/> + <UnitName Value="fpcunit"/> + <EditorIndex Value="1"/> + <TopLine Value="113"/> + <CursorPos X="21" Y="129"/> + <UsageCount Value="11"/> + <Loaded Value="True"/> + </Unit2> + <Unit3> + <Filename Value="..\uutlGenerics.pas"/> + <UnitName Value="uutlGenerics"/> + <EditorIndex Value="3"/> + <TopLine Value="308"/> + <CursorPos X="15" Y="111"/> + <UsageCount Value="11"/> + <Loaded Value="True"/> + </Unit3> + <Unit4> + <Filename Value="C:\Zusatzprogramme\Lazarus\fpc\2.7.1\source\rtl\inc\wstringh.inc"/> + <EditorIndex Value="-1"/> + <CursorPos X="11" Y="30"/> + <UsageCount Value="10"/> + </Unit4> + </Units> + <JumpHistory Count="29" HistoryIndex="28"> + <Position1> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="376" Column="30" TopLine="359"/> + </Position1> + <Position2> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="470" Column="13" TopLine="447"/> + </Position2> + <Position3> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="472" Column="41" TopLine="446"/> + </Position3> + <Position4> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="88" Column="23" TopLine="63"/> + </Position4> + <Position5> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="97" TopLine="86"/> + </Position5> + <Position6> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="86" Column="8" TopLine="74"/> + </Position6> + <Position7> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="101" Column="20" TopLine="88"/> + </Position7> + <Position8> + <Filename Value="..\uutlGenerics.pas"/> + <Caret Line="303" Column="50" TopLine="284"/> + </Position8> + <Position9> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="524" Column="14" TopLine="514"/> + </Position9> + <Position10> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="89" Column="50" TopLine="74"/> + </Position10> + <Position11> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="90" Column="37" TopLine="70"/> + </Position11> + <Position12> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="530" Column="33" TopLine="509"/> + </Position12> + <Position13> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="583" Column="32" TopLine="565"/> + </Position13> + <Position14> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="598" Column="5" TopLine="567"/> + </Position14> + <Position15> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="655" Column="19" TopLine="626"/> + </Position15> + <Position16> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="95" Column="29" TopLine="85"/> + </Position16> + <Position17> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="666" Column="45" TopLine="657"/> + </Position17> + <Position18> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="563" Column="23" TopLine="547"/> + </Position18> + <Position19> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="566" Column="23" TopLine="547"/> + </Position19> + <Position20> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="687" TopLine="663"/> + </Position20> + <Position21> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="686" Column="58" TopLine="670"/> + </Position21> + <Position22> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="700" Column="9" TopLine="682"/> + </Position22> + <Position23> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="704" Column="38" TopLine="692"/> + </Position23> + <Position24> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="700" Column="19" TopLine="685"/> + </Position24> + <Position25> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="612" TopLine="587"/> + </Position25> + <Position26> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="717" Column="17" TopLine="689"/> + </Position26> + <Position27> + <Filename Value="..\uutlGenerics.pas"/> + <Caret Line="1161" Column="3" TopLine="1148"/> + </Position27> + <Position28> + <Filename Value="..\uutlGenerics.pas"/> + <Caret Line="887" Column="3" TopLine="884"/> + </Position28> + <Position29> + <Filename Value="uGenericsTests.pas"/> + <Caret Line="658" Column="28" TopLine="652"/> + </Position29> + </JumpHistory> + </ProjectSession> +</CONFIG> diff --git a/Tests/UtilsTests.res b/Tests/UtilsTests.res new file mode 100644 index 0000000000000000000000000000000000000000..7c6cf3e4be6fa881cb2f2caa9bda693328155c28 GIT binary patch literal 138128 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 S)M$%T8;H+2FPFK8vi}DltTdMZ literal 0 HcmV?d00001 diff --git a/Tests/uGenericsTests.pas b/Tests/uGenericsTests.pas new file mode 100644 index 0000000..7ed05b8 --- /dev/null +++ b/Tests/uGenericsTests.pas @@ -0,0 +1,710 @@ +unit uGenericsTests; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fpcunit, testregistry, + uutlGenerics; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TTestObject = class + private + fData: Integer; + fOnDestroy: TNotifyEvent; + public + property Data: Integer read fData; + constructor Create(const aData: Integer; const aOnDestroy: TNotifyEvent); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlListTest = class(TTestCase) + private type + TTestList = specialize TutlList<TTestObject>; + private + fList: TTestList; + fTestObjs: array[0..9] of TTestObject; + procedure TestObjectDestroy(aSender: TObject); + protected + procedure SetUp; override; + procedure TearDown; override; + published + procedure GetItem; + procedure SetItem; + + procedure Add; + procedure Insert; + procedure IndexOf; + + procedure Exchange; + procedure Move; + + procedure Delete; + procedure Extract; + procedure Remove; + procedure Clear; + + procedure First; + procedure PushFirst; + procedure PopFirst; + + procedure Last; + procedure PushLast; + procedure PopLast; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlHashSetTest = class(TTestCase) + private type + TTestObjComparer = specialize TutlEventComparer<TTestObject>; + TTestHashSet = specialize TutlCustomHashSet<TTestObject>; + private + fHashSet: TTestHashSet; + fTestObjs: array[0..9] of TTestObject; + procedure TestObjectDestroy(aSender: TObject); + protected + procedure SetUp; override; + procedure TearDown; override; + public + function CompareTestObjects(const i1, i2: TTestObject): Integer; + published + procedure Add; + procedure Contains; + procedure IndexOf; + procedure Remove; + procedure Delete; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlMapTest = class(TTestCase) + private type + TTestMap = specialize TutlMap<Integer, TTestObject>; + private + fMap: TTestMap; + fTestObjs: array[0..9] of TTestObject; + fLastRemovedIndex: Integer; + procedure TestObjectDestroy(aSender: TObject); + function Key(const aIndex: Integer): Integer; + function CreateObj: TTestObject; + protected + procedure SetUp; override; + procedure TearDown; override; + + procedure AddExistingKey; + published + procedure GetValue; + procedure SetValue; + procedure GetValueAt; + procedure SetValueAt; + procedure GetKey; + procedure Add; + procedure IndexOf; + procedure Delete; + end; + + +implementation + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TTestObject/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TTestObject.Create(const aData: Integer; const aOnDestroy: TNotifyEvent); +begin + inherited Create; + fData := aData; + fOnDestroy := aOnDestroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TTestObject.Destroy; +begin + if Assigned(fOnDestroy) then + fOnDestroy(self); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlListTest////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.TestObjectDestroy(aSender: TObject); +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do + if (fTestObjs[i] = aSender) then + fTestObjs[i] := nil; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.SetUp; +var + i: Integer; +begin + inherited SetUp; + fList := TTestList.Create(true); + for i := Low(fTestObjs) to High(fTestObjs) do begin + fTestObjs[i] := TTestObject.Create(i, @TestObjectDestroy); + fList.Add(fTestObjs[i]); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.TearDown; +begin + FreeAndNil(fList); + inherited TearDown; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.GetItem; +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do + AssertTrue(fTestObjs[i] = fList[i - Low(fTestObjs)]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.SetItem; +var + o1, o2: TTestObject; +begin + o1 := fList[3]; + o2 := fList[6]; + fList[3] := o2; + fList[6] := o1; + AssertTrue(fList[6] = o1); + AssertTrue(fList[3] = o2); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Add; +var + t: TTestObject; + c: Integer; +begin + t := TTestObject.Create(123456, @TestObjectDestroy); + c := fList.Count; + fList.Add(t); + AssertEquals(c+1, fList.Count); + AssertTrue(fList[c] = t); + AssertTrue(fList.Last = t); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Insert; +var + t: TTestObject; + c: Integer; +begin + t := TTestObject.Create(123456, @TestObjectDestroy); + c := fList.Count; + fList.Insert(3, t); + AssertEquals(c+1, fList.Count); + AssertTrue(fList[3] = t); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.IndexOf; +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do + AssertEquals(i - Low(fTestObjs), fList.IndexOf(fTestObjs[i])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Exchange; +var + o1, o2: TTestObject; +begin + o1 := fList[3]; + o2 := fList[7]; + fList.Exchange(3, 7); + AssertTrue(fList[3] = o2); + AssertTrue(fList[7] = o1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Move; +begin + fList.Move(3, 6); + AssertTrue(fList[3] = fTestObjs[4]); + AssertTrue(fList[4] = fTestObjs[5]); + AssertTrue(fList[5] = fTestObjs[6]); + AssertTrue(fList[6] = fTestObjs[3]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Delete; +begin + fList.Delete(3); + AssertTrue(fTestObjs[3] = nil); + AssertEquals(Length(fTestObjs)-1, fList.Count); + + fList.OwnsObjects := false; + fList.Delete(4); + AssertTrue(fTestObjs[5] <> nil); + AssertEquals(Length(fTestObjs)-2, fList.Count); + AssertTrue(fList[4] = fTestObjs[6]); + FreeAndNil(fTestObjs[5]); + fList.OwnsObjects := true; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Extract; +var + o1, o2, o3: TTestObject; +begin + o1 := fList[1]; + o2 := TTestObject.Create(1234, @TestObjectDestroy); + o3 := fList.Extract(o1, o2); + try + AssertTrue(o1 = o3); + AssertEquals(Length(fTestObjs)-1, fList.Count); + AssertTrue(fTestObjs[1] <> nil); + finally + FreeAndNil(o1); + FreeAndNil(o2); + end; + + o1 := fList[1]; + o2 := TTestObject.Create(1234, @TestObjectDestroy); + o3 := fList.Extract(o2, o1); + try + AssertTrue(o1 = o3); + AssertEquals(Length(fTestObjs)-1, fList.Count); + finally + FreeAndNil(o2); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Remove; +var + o1: TTestObject; + i: Integer; +begin + o1 := fList[3]; + i := fList.Remove(o1); + AssertEquals(3, i); + AssertEquals(Length(fTestObjs)-1, fList.Count); + AssertTrue(fTestObjs[3] = nil); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Clear; +var + o: TTestObject; +begin + fList.Clear; + AssertEquals(0, fList.Count); + for o in fTestObjs do + AssertTrue(o = nil); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.First; +begin + AssertTrue(fTestObjs[Low(fTestObjs)] = fList.First); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.PushFirst; +var + o1: TTestObject; +begin + o1 := TTestObject.Create(1234, @TestObjectDestroy); + fList.PushFirst(o1); + AssertEquals(Length(fTestObjs)+1, fList.Count); + AssertTrue(fList.First = o1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.PopFirst; +var + o1: TTestObject; +begin + o1 := fList.PopFirst; + AssertEquals(Length(fTestObjs)-1, fList.Count); + AssertTrue(o1 = fTestObjs[0]); + FreeAndNil(o1); + + o1 := fList.PopFirst(true); + AssertEquals(Length(fTestObjs)-2, fList.Count); + AssertTrue(o1 = nil); + AssertTrue(fTestObjs[1] = nil); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.Last; +begin + AssertTrue(fTestObjs[High(fTestObjs)] = fList.Last); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.PushLast; +var + o1: TTestObject; +begin + o1 := TTestObject.Create(1234, @TestObjectDestroy); + fList.PushLast(o1); + AssertEquals(Length(fTestObjs)+1, fList.Count); + AssertTrue(fList.Last = o1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListTest.PopLast; +var + o1: TTestObject; +begin + o1 := fList.PopLast; + AssertEquals(Length(fTestObjs)-1, fList.Count); + AssertTrue(o1 = fTestObjs[High(fTestObjs)]); + FreeAndNil(o1); + + o1 := fList.PopLast(true); + AssertEquals(Length(fTestObjs)-2, fList.Count); + AssertTrue(o1 = nil); + AssertTrue(fTestObjs[High(fTestObjs)-1] = nil); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlHashSetTest/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.TestObjectDestroy(aSender: TObject); +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do + if (fTestObjs[i] = aSender) then + fTestObjs[i] := nil; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.SetUp; +var + i: Integer; +begin + inherited SetUp; + fHashSet := TTestHashSet.Create(TTestObjComparer.Create(@CompareTestObjects), true); + for i := Low(fTestObjs) to High(fTestObjs) do begin + fTestObjs[i] := TTestObject.Create(i, @TestObjectDestroy); + fHashSet.Add(fTestObjs[i]); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.TearDown; +begin + FreeAndNil(fHashSet); + inherited TearDown; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlHashSetTest.CompareTestObjects(const i1, i2: TTestObject): Integer; +begin + if (i1.Data < i2.Data) then + result := -1 + else if (i1.Data > i2.Data) then + result := 1 + else + result := 0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.Add; +var + o1: TTestObject; + b: Boolean; +begin + o1 := TTestObject.Create(1234, @TestObjectDestroy); + b := fHashSet.Add(o1); + AssertTrue(b); + AssertEquals(Length(fTestObjs)+1, fHashSet.Count); + + b := fHashSet.Add(o1); + AssertFalse(b); + AssertEquals(Length(fTestObjs)+1, fHashSet.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.Contains; +var + o1: TTestObject; + b: Boolean; +begin + o1 := TTestObject.Create(1234, @TestObjectDestroy); + try + b := fHashSet.Contains(fTestObjs[0]); + AssertTrue(b); + + b := fHashSet.Contains(o1); + AssertFalse(b); + finally + FreeAndNil(o1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.IndexOf; +var + o1: TTestObject; + i: Integer; +begin + o1 := TTestObject.Create(1234, @TestObjectDestroy); + try + i := fHashSet.IndexOf(fTestObjs[4]); + AssertEquals(4, i); + + i := fHashSet.IndexOf(o1); + AssertEquals(-1, i); + finally + FreeAndNil(o1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.Remove; +var + b: Boolean; +begin + b := fHashSet.Remove(fTestObjs[5]); + AssertTrue(fTestObjs[5] = nil); + AssertTrue(b); + AssertEquals(Length(fTestObjs)-1, fHashSet.Count); + + fHashSet.OwnsObjects := false; + try + b := fHashSet.Remove(fTestObjs[0]); + AssertTrue(fTestObjs[0] <> nil); + AssertEquals(Length(fTestObjs)-2, fHashSet.Count); + AssertTrue(b); + + b := fHashSet.Remove(fTestObjs[0]); + AssertFalse(b); + AssertEquals(Length(fTestObjs)-2, fHashSet.Count); + finally + FreeAndNil(fTestObjs[0]); + fHashSet.OwnsObjects := true; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlHashSetTest.Delete; +begin + fHashSet.Delete(0); + AssertEquals(Length(fTestObjs)-1, fHashSet.Count); + AssertTrue(fTestObjs[0] = nil); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMapTest/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.TestObjectDestroy(aSender: TObject); +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do + if (fTestObjs[i] = aSender) then begin + fLastRemovedIndex := i; + fTestObjs[i] := nil; + exit; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMapTest.Key(const aIndex: Integer): Integer; +begin + result := fTestObjs[aIndex].Data; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMapTest.CreateObj: TTestObject; +var + k: Integer; +begin + repeat + k := random(10000); + until not fMap.Contains(k); + result := TTestObject.Create(k, @TestObjectDestroy); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.SetUp; +var + i: Integer; + o: TTestObject; +begin + inherited SetUp; + fMap := TTestMap.Create(true); + Randomize; + for i := Low(fTestObjs) to High(fTestObjs) do begin + o := CreateObj; + fTestObjs[i] := o; + fMap.Add(o.Data, o); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.TearDown; +begin + FreeAndNil(fMap); + inherited TearDown; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.AddExistingKey; +var + o1: TTestObject; +begin + o1 := TTestObject.Create(fTestObjs[0].Data, @TestObjectDestroy); + try + fMap.Add(o1.Data, o1); + finally + FreeAndNil(o1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.GetValue; +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do + AssertTrue(fMap[Key(i)] = fTestObjs[i]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.SetValue; +var + o1, o2: TTestObject; +begin + o1 := fMap[Key(2)]; + o2 := CreateObj; + fMap[Key(2)] := o2; + try + AssertTrue(fMap[Key(2)] = o2); + finally + FreeAndNil(o1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.GetValueAt; +type + TIntList = specialize TutlList<Integer>; + TIntComparer = specialize TutlComparer<Integer>; +var + o: TTestObject; + l: TIntList; + i: Integer; +begin + l := TIntList.Create; + try + for o in fTestObjs do + l.Add(o.Data); + l.Sort(TIntComparer.Create); + + for i := 0 to l.Count-1 do + AssertEquals(l[i], fMap.ValueAt[i].Data); + finally + FreeAndNil(l); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.SetValueAt; +var + o1, o2: TTestObject; +begin + o1 := fMap.ValueAt[4]; + o2 := TTestObject.Create(o1.Data, @TestObjectDestroy); + fMap.ValueAt[4] := o2; + try + AssertTrue(fMap.ValueAt[4] = o2); + finally + FreeAndNil(o1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.GetKey; +type + TIntList = specialize TutlList<Integer>; + TIntComparer = specialize TutlComparer<Integer>; +var + o: TTestObject; + l: TIntList; + i: Integer; +begin + l := TIntList.Create; + try + for o in fTestObjs do + l.Add(o.Data); + l.Sort(TIntComparer.Create); + + for i := 0 to l.Count-1 do + AssertEquals(l[i], fMap.Keys[i]); + finally + FreeAndNil(l); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.Add; +var + o1: TTestObject; +begin + o1 := CreateObj; + fMap.Add(o1.Data, o1); + AssertEquals(Length(fTestObjs)+1, fMap.Count); + + AssertException(EutlMap, @AddExistingKey); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.IndexOf; +type + TIntList = specialize TutlList<Integer>; + TIntComparer = specialize TutlComparer<Integer>; +var + o: TTestObject; + l: TIntList; +begin + l := TIntList.Create; + try + for o in fTestObjs do + l.Add(o.Data); + l.Sort(TIntComparer.Create); + + for o in fTestObjs do + AssertEquals(l.IndexOf(o.Data), fMap.IndexOf(o.Data)); + finally + FreeAndNil(l); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMapTest.Delete; +var + i: Integer; +begin + for i := Low(fTestObjs) to High(fTestObjs) do begin + fMap.Delete(Key(i)); + AssertNull(fTestObjs[i]); + AssertEquals('Count', Length(fTestObjs)-i-1, fMap.Count); + AssertEquals('Index', fLastRemovedIndex, i); + end; +end; + +initialization + RegisterTest(TutlListTest); + RegisterTest(TutlHashSetTest); + RegisterTest(TutlMapTest); + +end. + diff --git a/uutlCommon.pas b/uutlCommon.pas new file mode 100644 index 0000000..6ea35a4 --- /dev/null +++ b/uutlCommon.pas @@ -0,0 +1,394 @@ +unit uutlCommon; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit implementiert allgemein nützliche nicht-generische Klassen } + +{$mode objfpc}{$H+} +{$modeswitch nestedprocvars} + +interface + +uses + Classes, SysUtils, syncobjs, versionresource, versiontypes, typinfo, uutlGenerics + {$IFDEF UNIX}, unixtype, pthreads {$ENDIF}; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlStringStack = class(TStringList) + public + procedure Push(const aStr: String); + function Pop: String; + function Seek: String; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlInterfaceNoRefCount = class(TObject, IUnknown) + protected + fRefCount : longint; + { implement methods of IUnknown } + function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; + function _AddRef : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; + function _Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; + public + property RefCount: LongInt read fRefCount; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlCSVList = class(TStringList) + private + FSkipDelims: boolean; + function GetStrictDelText: string; + procedure SetStrictDelText(const Value: string); + public + property StrictDelimitedText: string read GetStrictDelText write SetStrictDelText; + // Skip repeated delims instead of reading empty lines? + property SkipDelims: boolean read FSkipDelims write FSkipDelims; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlCheckSynchronizeEvent = class(TObject) + private + fEvent: TEvent; + function WaitMainThread(const aTimeout: Cardinal): TWaitResult; + public const + MAIN_WAIT_GRANULARITY = 10; + public + procedure SetEvent; + procedure ResetEvent; + function WaitFor(const aTimeout: Cardinal): TWaitResult; + + constructor Create(const aEventAttributes: syncobjs.PSecurityAttributes; + const aManualReset, aInitialState: Boolean; const aName: string); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlBaseEventList = specialize TutlList<TutlCheckSynchronizeEvent>; + TutlEventList = class(TutlBaseEventList) + public + function AddEvent(const aEventAttributes: syncobjs.PSecurityAttributes; const aManualReset, + aInitialState: Boolean; const aName : string): TutlCheckSynchronizeEvent; + function AddDefaultEvent: TutlCheckSynchronizeEvent; + function WaitAll(const aTimeout: Cardinal): TWaitResult; + + constructor Create; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlVersionInfo = class(TObject) + private + fVersionRes: TVersionResource; + function GetFixedInfo: TVersionFixedInfo; + function GetStringFileInfo: TVersionStringFileInfo; + function GetVarFileInfo: TVersionVarFileInfo; + public + property FixedInfo: TVersionFixedInfo read GetFixedInfo; + property StringFileInfo: TVersionStringFileInfo read GetStringFileInfo; + property VarFileInfo: TVersionVarFileInfo read GetVarFileInfo; + + function Load(const aInstance: THandle): Boolean; + + constructor Create; + destructor Destroy; override; + end; + +function utlEventEqual(const aEvent1, aEvent2): Boolean; + +implementation + +uses + {uutlTiming needs to be included after Windows because of GetTickCount64} + uutlLogger{$IFDEF WINDOWS},Windows{$ENDIF}, uutlTiming; + +{$IFNDEF WINDOWS} +function CharNext(const C: PChar): PChar; +begin + //TODO: prüfen ob das für UnicodeString auch stimmt + Result:= C; + if Result^>#0 then + inc(Result); +end; +{$IFEND} + +function utlEventEqual(const aEvent1, aEvent2): Boolean; +begin + result := + (TMethod(aEvent1).Code = TMethod(aEvent2).Code) and + (TMethod(aEvent1).Data = TMethod(aEvent2).Data); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlStringStack////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlStringStack.Push(const aStr: String); +begin + Insert(0, aStr); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlStringStack.Pop: String; +begin + result := ''; + if Count > 0 then begin + result := Strings[0]; + Delete(0); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlStringStack.Seek: String; +begin + result := ''; + if Count > 0 then + result := Strings[0]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlInterfaceNoRefCount/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceNoRefCount.QueryInterface(constref iid: tguid; out obj): longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + if getinterface(iid,obj) then + result:=S_OK + else + result:=longint(E_NOINTERFACE); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceNoRefCount._AddRef: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + result := InterLockedIncrement(fRefCount); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceNoRefCount._Release: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + result := InterLockedDecrement(fRefCount); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCSVList/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCSVList.GetStrictDelText: string; +var + S: string; + I, J, Cnt: Integer; + q: boolean; + LDelimiters: TSysCharSet; +begin + Cnt := GetCount; + if (Cnt = 1) and (Get(0) = '') then + Result := QuoteChar + QuoteChar + else + begin + Result := ''; + LDelimiters := [QuoteChar, Delimiter]; + for I := 0 to Cnt - 1 do + begin + S := Get(I); + q:= false; + if S>'' then begin + for J:= 1 to length(S) do + if S[J] in LDelimiters then begin + q:= true; + break; + end; + if q then S := AnsiQuotedStr(S, QuoteChar); + end else + S := AnsiQuotedStr(S, QuoteChar); + Result := Result + S + Delimiter; + end; + System.Delete(Result, Length(Result), 1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCSVList.SetStrictDelText(const Value: string); +var + S: String; + P, P1: PChar; +begin + BeginUpdate; + try + Clear; + P:= PChar(Value); + if FSkipDelims then begin + while (P^<>#0) and (P^=Delimiter) do begin + P:= CharNext(P); + end; + end; + while (P^<>#0) do begin + if (P^ = QuoteChar) then begin + S:= AnsiExtractQuotedStr(P, QuoteChar); + end else begin + P1:= P; + while (P^<>#0) and (P^<>Delimiter) do begin + P:= CharNext(P); + end; + SetString(S, P1, P - P1); + end; + Add(S); + while (P^<>#0) and (P^<>Delimiter) do begin + P:= CharNext(P); + end; + if (P^<>#0) then + P:= CharNext(P); + if FSkipDelims then begin + while (P^<>#0) and (P^=Delimiter) do begin + P:= CharNext(P); + end; + end; + end; + finally + EndUpdate; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCheckSynchronizeEvent///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCheckSynchronizeEvent.WaitMainThread(const aTimeout: Cardinal): TWaitResult; +var + timeout: qword; +begin + timeout:= GetTickCount64 + aTimeout; + repeat + result := fEvent.WaitFor(TutlCheckSynchronizeEvent.MAIN_WAIT_GRANULARITY); + CheckSynchronize(); + until (result <> wrTimeout) or ((GetTickCount64 > timeout) and (aTimeout <> INFINITE)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCheckSynchronizeEvent.SetEvent; +begin + fEvent.SetEvent; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCheckSynchronizeEvent.ResetEvent; +begin + fEvent.ResetEvent; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCheckSynchronizeEvent.WaitFor(const aTimeout: Cardinal): TWaitResult; +begin + if (GetCurrentThreadId = MainThreadID) then + result := WaitMainThread(aTimeout) + else + result := fEvent.WaitFor(aTimeout); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCheckSynchronizeEvent.Create(const aEventAttributes: syncobjs.PSecurityAttributes; + const aManualReset, aInitialState: Boolean; const aName: string); +begin + inherited Create; + fEvent := TEvent.Create(aEventAttributes, aManualReset, aInitialState, aName); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlCheckSynchronizeEvent.Destroy; +begin + FreeAndNil(fEvent); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlEventList///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlEventList.AddEvent(const aEventAttributes: syncobjs.PSecurityAttributes; const aManualReset, + aInitialState: Boolean; const aName: string): TutlCheckSynchronizeEvent; +begin + result := TutlCheckSynchronizeEvent.Create(aEventAttributes, aManualReset, aInitialState, aName); + Add(result); +end; + +function TutlEventList.AddDefaultEvent: TutlCheckSynchronizeEvent; +begin + result := AddEvent(nil, true, false, ''); + result.ResetEvent; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlEventList.WaitAll(const aTimeout: Cardinal): TWaitResult; +var + i: integer; + timeout, tick: qword; +begin + timeout := GetTickCount64 + aTimeout; + for i := 0 to Count-1 do begin + if (aTimeout <> INFINITE) then begin + tick := GetTickCount64; + if (tick >= timeout) then begin + result := wrTimeout; + exit; + end else + result := Items[i].WaitFor(timeout - tick); + end else + result := Items[i].WaitFor(INFINITE); + if result <> wrSignaled then + exit; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventList.Create; +begin + inherited Create(true); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlVersionInfo/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlVersionInfo.GetFixedInfo: TVersionFixedInfo; +begin + result := fVersionRes.FixedInfo; +end; + +function TutlVersionInfo.GetStringFileInfo: TVersionStringFileInfo; +begin + result := fVersionRes.StringFileInfo; +end; + +function TutlVersionInfo.GetVarFileInfo: TVersionVarFileInfo; +begin + result := fVersionRes.VarFileInfo; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlVersionInfo.Load(const aInstance: THandle): Boolean; +var + Stream: TResourceStream; +begin + result := false; + if (FindResource(aInstance, PChar(PtrInt(1)), PChar(RT_VERSION)) = 0) then + exit; + Stream := TResourceStream.CreateFromID(aInstance, 1, PChar(RT_VERSION)); + try + fVersionRes.SetCustomRawDataStream(Stream); + fVersionRes.FixedInfo;// access some property to force load from the stream + fVersionRes.SetCustomRawDataStream(nil); + finally + Stream.Free; + end; + result := true; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlVersionInfo.Create; +begin + inherited Create; + fVersionRes := TVersionResource.Create; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlVersionInfo.Destroy; +begin + FreeAndNil(fVersionRes); + inherited Destroy; +end; + +end. + diff --git a/uutlConsoleHelper.pas b/uutlConsoleHelper.pas new file mode 100644 index 0000000..c90fa06 --- /dev/null +++ b/uutlConsoleHelper.pas @@ -0,0 +1,2128 @@ +unit uutlConsoleHelper; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit implementiert Helper Klassen für Consolen Ein- und Ausgaben, + sowie Menüführung und Autovervollständigung } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, fgl, uutlMCF, uutlCommon, uutlGenerics, syncobjs; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlParameterStringFlag = (psfBrackets); + TutlParameterStringFlags = set of TutlParameterStringFlag; + TutlMenuItem = class; + TutlMenuParameter = class + private + fParent: TutlMenuItem; + fOptional: Boolean; + fName, fDescription, fValue: String; + public + property Parent: TutlMenuItem read fParent; + property Optional: Boolean read fOptional; + property Value: String read fValue; + property Name: String read fName; + property Description: String read fDescription; + + procedure WriteConfig(const aMCF: TutlMCFSection); virtual; + procedure ReadConfig(const aMCF: TutlMCFSection); virtual; + + procedure GetAutoCompleteStrings(const aStrings: TStrings); virtual; abstract; + function SetValue(const aValue: String): Boolean; virtual; + function GetString(const aOptions: TutlParameterStringFlags = [psfBrackets]): String; virtual; + + constructor Create(const aOptional: Boolean; const aName, aDescription: String); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlMenuParameterListBase = specialize TFPGObjectList<TutlMenuParameter>; + TutlMenuParameterList = class(TutlMenuParameterListBase) + public + function HasParameter(const aName: String): Boolean; + function FindParameter(const aName: String): TutlMenuParameter; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlMenuParameterStack = class(TutlMenuParameterList) + public + procedure Push(const aValue: TutlMenuParameter); + function Seek: TutlMenuParameter; + function Pop: TutlMenuParameter; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlParameterType = (ptString = 0, ptInteger, ptBoolean, ptHex, ptPreset); + TutlParameterTypeH = specialize TutlEnumHelper<TutlParameterType>; + TutlMenuParameterSingle = class(TutlMenuParameter) + private + fType: TutlParameterType; + public + property ParamType: TutlParameterType read fType; + + procedure WriteConfig(const aMCF: TutlMCFSection); override; + procedure ReadConfig(const aMCF: TutlMCFSection); override; + + procedure GetAutoCompleteStrings(const aStrings: TStrings); override; + function SetValue(const aValue: String): Boolean; override; + function GetString(const aOptions: TutlParameterStringFlags = [psfBrackets]): String; override; + + constructor Create(const aOptional: Boolean; const aName, aDescription: String; const aType: TutlParameterType); + destructor Destroy; override; + end; + TutlMenuParameterSingleList = specialize TFPGObjectList<TutlMenuParameterSingle>; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlMenuParameterGroup = class(TutlMenuParameter) + private + fParameters: TutlMenuParameterSingleList; + function GetCount: Integer; + function GetParameter(const aIndex: Integer): TutlMenuParameterSingle; + public + property Count: Integer read GetCount; + property Parameter[const aIndex: Integer]: TutlMenuParameterSingle read GetParameter; default; + + procedure WriteConfig(const aMCF: TutlMCFSection); override; + procedure ReadConfig(const aMCF: TutlMCFSection); override; + + procedure GetAutoCompleteStrings(const aStrings: TStrings); override; + function SetValue(const aValue: String): Boolean; override; + function GetString(const aOptions: TutlParameterStringFlags = [psfBrackets]): String; override; + + function AddParameter(const aName, aDescription: String; + const aType: TutlParameterType): TutlMenuParameterSingle; overload; + function AddParameter(const aParameter: TutlMenuParameterSingle): TutlMenuParameterSingle; overload; + procedure DelParameter(const aIndex: Integer); + + constructor Create(const aOptional: Boolean; const aName, aDescription: String); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlCallback = procedure(aSender: TObject) of object; + TutlMenuItemList = specialize TFPGObjectList<TutlMenuItem>; + TutlMenuItem = class(TObject) + private + fCommand: String; + fDescription: String; + fCallback: TutlCallback; + fExecutable: Boolean; + fParent: TutlMenuItem; + fHelpItem: TutlMenuItem; + + function GetCount: Integer; + function GetItems(const aIndex: Integer): TutlMenuItem; + function GetMenuPath: String; + function GetParamCount: Integer; + function GetParameters(const aIndex: Integer): TutlMenuParameter; + function GetParameterString: String; + procedure SetCallback(aValue: TutlCallback); + protected + fItems: TutlMenuItemList; + fParameters: TutlMenuParameterList; + + procedure WriteConfig(const aMCF: TutlMCFSection); virtual; + procedure ReadConfig(const aMCF: TutlMCFSection); virtual; + public + property Command: String read fCommand write fCommand; + property Description: String read fDescription write fDescription; + property Callback: TutlCallback read fCallback write SetCallback; + property Executable: Boolean read fExecutable; + property MenuPath: String read GetMenuPath; + property ParameterString: String read GetParameterString; + property ParamCount: Integer read GetParamCount; + property Count: Integer read GetCount; + property Parent: TutlMenuItem read fParent; + property Items[const aIndex: Integer]: TutlMenuItem read GetItems; default; + property Parameters[const aIndex: Integer]: TutlMenuParameter read GetParameters; + + procedure GetAutoCompleteStrings(const aList: TStrings; const aParameter: TutlMenuParameterList); virtual; + function GetString: String; + function AddItem(const aCmd, aDesc: String; const aCallback: TutlCallback): TutlMenuItem; overload; + function AddItem(const aItem: TutlMenuItem): TutlMenuItem; overload; + procedure DelItem(const aIndex: Integer); + function AddParameter(const aParameter: TutlMenuParameter): TutlMenuParameter; + procedure DelParameter(const aIndex: Integer); + + procedure LoadFromStream(const aStream: TStream); + procedure SaveToStream(const aStream: TStream); + + constructor Create(const aParent: TutlMenuItem; const aCmd, aDesc: String; const aCallback: TutlCallback); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlParseResult = (prUnknownCommand, prInvalidParam, prInvalidParamCount, prSuccess, prIncompleteCmd); + TutlHelpOption = (hoNone, hoAll, hoDetail); + TutlCommandMenu = class(TutlMenuItem) + private + fInvalidParam: String; + fUnknownCmd: String; + fLastCmd: String; + fCurrentParamCount: Integer; + function GetCmdParameter: TutlMenuParameterList; + protected + fCmdParameter: TutlMenuParameterStack; + fCmdStack: TutlStringStack; + fCurrentMenu: TutlMenuItem; + procedure SplitCmdString(const aText: String; const aChar: Char); + function ParseCommand: TutlParseResult; + public + property CmdParameter: TutlMenuParameterList read GetCmdParameter; + property LastCmd: String read fLastCmd; + + procedure ExecuteCommand(const aCmd: String); virtual; + + procedure DisplayHelp(const aRefMenu: TutlMenuItem = nil; const aOption: TutlHelpOption = hoNone); + procedure DisplayIncompleteCommand; + procedure DisplayUnknownCommand(const aCmd: String = ''); + procedure DisplayInvalidParamCount(const aParamCount: Integer = -1); + procedure DisplayInvalidParam(const aParam: String = ''); + + constructor Create(const aHelp: String); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlInputEvent = procedure(aSender: TObject; const aInput: String) of Object; + TutlAutoCompleteEvent = function(aSender: TObject; const aInput: String; const aDisplayPossibilities: Boolean): String of Object; + TutlCommandPrompt = class(TObject) + private + fCurrent: String; + fInput: String; + fPrefix: String; + fHistoryBackup: String; + fCurID: Integer; + fStartIndex: Integer; + fHistoryID: Integer; + fHistoryEnabled: Boolean; + fRunning: Boolean; + fHiddenChar: Char; + fConsoleCS: TCriticalSection; + fHistory: TStringList; + + fOnInput: TutlInputEvent; + fOnAutoComplete: TutlAutoCompleteEvent; + + function GetCurrent: String; + procedure SetCurrent(aValue: String); + procedure SetHiddenChar(aValue: Char); + procedure SetPrefix(aValue: String); + + procedure CursorToStart; + procedure CursorToEnd; + procedure DelInput(const aAll: Boolean = false); + procedure RestoreInput; + procedure CursorRight; + procedure CursorLeft; + procedure DelChar(const aBeforeCursor: Boolean = false); + procedure WriteChar(const c: Char); + + function ReadLnEx: String; + function AutoComplete(const aInput: String; const aDisplayPossibilities: Boolean): String; + procedure AddHistory(const aInput: String); + + procedure DoInput; + public + property Prefix: String read fPrefix write SetPrefix; + property Current: String read GetCurrent write SetCurrent; + property HistoryEnabled: Boolean read fHistoryEnabled write fHistoryEnabled; + property HiddenChar: Char read fHiddenChar write SetHiddenChar; + property OnInput: TutlInputEvent read fOnInput write fOnInput; + property OnAutoComplete: TutlAutoCompleteEvent read fOnAutoComplete write fOnAutoComplete; + + procedure Start; + procedure Stop; + procedure Reset; + procedure Clear; //löscht nur die Ausgabe und hält die Eingabe intern + procedure Restore; //stellt die Ausgabe wieder her + + constructor Create(const aConsoleCS: TCriticalSection); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlConsoleMenu = class(TutlCommandMenu) + private + fExitMenu: TutlMenuItem; + fCommandPrompt: TutlCommandPrompt; + fIsAsking: Boolean; + fInputBackup: String; + + fOnAnswer: TutlInputEvent; + + procedure CommandInput(aSender: TObject; const aCmd: String); + function AutoComplete(aSender: TObject; const aCmd: String; + const aDisplayPossibilities: Boolean): String; + + procedure DoAnswer(const aInput: String); + public + property CommandPrompt: TutlCommandPrompt read fCommandPrompt; + property OnAnswer: TutlInputEvent read fOnAnswer; + + procedure ExecuteCommand(const aCmd: String); override; + procedure StartMenu; + procedure ExitMenu; + procedure Ask(const aQuestion: String; const aHidden: Boolean = false; const aOnAnswer: TutlInputEvent = nil); + + constructor Create(const aHelp: String; const aConsoleCS: TCriticalSection); + destructor Destroy; override; + end; + +implementation + +uses + strutils, + uutlLogger, + uutlKeyCodes, + {$IFDEF WINDOWS} + windows + {$ELSE} + crt + {$ENDIF}; + +const + COMMAND_PROMPT_PREFIX = '> '; + +{$IFDEF WINDOWS} +var + ScanCode : char; + SpecialKey : boolean; + DoingNumChars: Boolean; + DoingNumCode: Byte; + +Function RemapScanCode (ScanCode: byte; CtrlKeyState: byte; keycode:longint): byte; + { Several remappings of scancodes are necessary to comply with what + we get with MSDOS. Special Windows keys, as Alt-Tab, Ctrl-Esc etc. + are excluded } +var + AltKey, CtrlKey, ShiftKey: boolean; +const + { + Keypad key scancodes: + + Ctrl Norm + + $77 $47 - Home + $8D $48 - Up arrow + $84 $49 - PgUp + $8E $4A - - + $73 $4B - Left Arrow + $8F $4C - 5 + $74 $4D - Right arrow + $4E $4E - + + $75 $4F - End + $91 $50 - Down arrow + $76 $51 - PgDn + $92 $52 - Ins + $93 $53 - Del + } + CtrlKeypadKeys: array[$47..$53] of byte = + ($77, $8D, $84, $8E, $73, $8F, $74, $4E, $75, $91, $76, $92, $93); + +begin + AltKey := ((CtrlKeyState AND + (RIGHT_ALT_PRESSED OR LEFT_ALT_PRESSED)) > 0); + CtrlKey := ((CtrlKeyState AND + (RIGHT_CTRL_PRESSED OR LEFT_CTRL_PRESSED)) > 0); + ShiftKey := ((CtrlKeyState AND SHIFT_PRESSED) > 0); + + if AltKey then + begin + case ScanCode of + // Digits, -, = + $02..$0D: inc(ScanCode, $76); + // Function keys + $3B..$44: inc(Scancode, $2D); + $57..$58: inc(Scancode, $34); + // Extended cursor block keys + $47..$49, $4B, $4D, $4F..$53: + inc(Scancode, $50); + // Other keys + $1C: Scancode := $A6; // Enter + $35: Scancode := $A4; // / (keypad and normal!) + end + end + else if CtrlKey then + case Scancode of + // Tab key + $0F: Scancode := $94; + // Function keys + $3B..$44: inc(Scancode, $23); + $57..$58: inc(Scancode, $32); + // Keypad keys + $35: Scancode := $95; // \ + $37: Scancode := $96; // * + $47..$53: Scancode := CtrlKeypadKeys[Scancode]; + //Enter on Numpad + $1C: + begin + Scancode := $0A; + SpecialKey := False; + end; + end + else if ShiftKey then + case Scancode of + // Function keys + $3B..$44: inc(Scancode, $19); + $57..$58: inc(Scancode, $30); + //Enter on Numpad + $1C: + begin + Scancode := $0D; + SpecialKey := False; + end; + end + else + case Scancode of + // Function keys + $57..$58: inc(Scancode, $2E); // F11 and F12 + //Enter on NumPad + $1C: + begin + Scancode := $0D; + SpecialKey := False; + end; + end; + RemapScanCode := ScanCode; +end; + + +function KeyPressed : boolean; +var + nevents,nread : dword; + buf : TINPUTRECORD; + AltKey: Boolean; + c : longint; +begin + KeyPressed := FALSE; + if ScanCode <> #0 then + KeyPressed := TRUE + else + begin + GetNumberOfConsoleInputEvents(TextRec(input).Handle,nevents{%H-}); + while nevents>0 do + begin + ReadConsoleInputA(TextRec(input).Handle,buf{%H-},1,nread{%H-}); + if buf.EventType = KEY_EVENT then + if buf.Event.KeyEvent.bKeyDown then + begin + { Alt key is VK_MENU } + { Capslock key is VK_CAPITAL } + + AltKey := ((Buf.Event.KeyEvent.dwControlKeyState AND + (RIGHT_ALT_PRESSED OR LEFT_ALT_PRESSED)) > 0); + if not(Buf.Event.KeyEvent.wVirtualKeyCode in [VK_SHIFT, VK_MENU, VK_CONTROL, + VK_CAPITAL, VK_NUMLOCK, + VK_SCROLL]) then + begin + keypressed:=true; + + if (ord(buf.Event.KeyEvent.AsciiChar) = 0) or + (buf.Event.KeyEvent.dwControlKeyState and (LEFT_ALT_PRESSED or ENHANCED_KEY) > 0) then + begin + SpecialKey := TRUE; + ScanCode := Chr(RemapScanCode(Buf.Event.KeyEvent.wVirtualScanCode, Buf.Event.KeyEvent.dwControlKeyState, + Buf.Event.KeyEvent.wVirtualKeyCode)); + end + else + begin + { Map shift-tab } + if (buf.Event.KeyEvent.AsciiChar=#9) and + (buf.Event.KeyEvent.dwControlKeyState and SHIFT_PRESSED > 0) then + begin + SpecialKey := TRUE; + ScanCode := #15; + end + else + begin + SpecialKey := FALSE; + ScanCode := Chr(Ord(buf.Event.KeyEvent.AsciiChar)); + end; + end; + + if AltKey then + begin + case Buf.Event.KeyEvent.wVirtualScanCode of + 71 : c:=7; + 72 : c:=8; + 73 : c:=9; + 75 : c:=4; + 76 : c:=5; + 77 : c:=6; + 79 : c:=1; + 80 : c:=2; + 81 : c:=3; + 82 : c:=0; + else + break; + end; + DoingNumChars := true; + DoingNumCode := Byte((DoingNumCode * 10) + c); + Keypressed := false; + Specialkey := false; + ScanCode := #0; + end + else + break; + end; + end + else + begin + if (Buf.Event.KeyEvent.wVirtualKeyCode in [VK_MENU]) then + if DoingNumChars then + if DoingNumCode > 0 then + begin + ScanCode := Chr(DoingNumCode); + Keypressed := true; + + DoingNumChars := false; + DoingNumCode := 0; + break + end; { if } + end; + { if we got a key then we can exit } + if keypressed then + exit; + GetNumberOfConsoleInputEvents(TextRec(input).Handle,nevents); + end; + end; +end; + +function ReadKey: char; +begin + while (not KeyPressed) do + Sleep(1); + if SpecialKey then begin + ReadKey := #0; + SpecialKey := FALSE; + end else begin + ReadKey := ScanCode; + ScanCode := #0; + end; +end; +{$ENDIF} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameter//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameter.WriteConfig(const aMCF: TutlMCFSection); +begin + with aMCF do begin + SetString('classname', self.ClassName); + SetBool ('optional', fOptional); + SetString('name', fName); + SetString('description', fDescription); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameter.ReadConfig(const aMCF: TutlMCFSection); +begin + with aMCF do begin + fOptional := GetBool ('optional', true); + fName := GetString('name', fName); + fDescription := GetString('description', fDescription); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameter.SetValue(const aValue: String): Boolean; +begin + fValue := aValue; + result := true; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameter//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameter.GetString(const aOptions: TutlParameterStringFlags + ): String; +begin + if fOptional then + result := '(' + fName + ')' + else + result := '[' + fName + ']' +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMenuParameter.Create(const aOptional: Boolean; const aName, aDescription: String); +begin + inherited Create; + fOptional := aOptional; + fName := aName; + fDescription := aDescription; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMenuParameter.Destroy; +begin + inherited Destroy; +end; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameterList//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterList.HasParameter(const aName: String): Boolean; +begin + result := Assigned(FindParameter(aName)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterList.FindParameter(const aName: String): TutlMenuParameter; +var + i: Integer; +begin + for i := 0 to Count-1 do begin + result := Items[i]; + if (result.Name = aName) then + exit; + end; + result := nil +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameterStack/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterStack.Push(const aValue: TutlMenuParameter); +begin + Add(aValue); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterStack.Seek: TutlMenuParameter; +begin + if (Count > 0) then + result := Items[Count-1] + else + result := nil; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterStack.Pop: TutlMenuParameter; +begin + if (Count > 0) then begin + result := Items[Count-1]; + Delete(Count-1); + end else + result := nil; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterSingle.WriteConfig(const aMCF: TutlMCFSection); +begin + inherited WriteConfig(aMCF); + aMCF.SetString('type', TutlParameterTypeH.ToString(fType)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterSingle.ReadConfig(const aMCF: TutlMCFSection); +begin + inherited ReadConfig(aMCF); + fType := TutlParameterTypeH.ToEnum(aMCF.GetString('type', TutlParameterTypeH.ToString(ptPreset))); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameterSingle////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterSingle.GetAutoCompleteStrings(const aStrings: TStrings); +begin + case fType of + ptBoolean: begin + aStrings.Add('true'); + aStrings.Add('false'); + end; + ptInteger: begin + aStrings.Add('[integer]'); + end; + ptHex: begin + aStrings.Add('[hex]'); + end; + ptString: begin + aStrings.Add('[string]'); + end; + ptPreset: begin + aStrings.Add(fName); + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterSingle.SetValue(const aValue: String): Boolean; +const + BOOL_ARR: array[0..7] of String = ('y', 'n', 't', 'f', 'yes', 'no', 'true', 'false'); + + function IsInArr: Boolean; + var + i: Integer; + s: String; + begin + s := LowerCase(aValue); + result := true; + for i := 0 to high(BOOL_ARR) do + if (BOOL_ARR[i] = s) then + exit; + result := false; + end; + +var + i: Integer; + c: QWord; +begin + result := false; + case fType of + ptBoolean: + if IsInArr then + result := true; + ptInteger: + result := TryStrToInt(aValue, i); + ptHex: + result := AnsiStartsStr('0x', aValue) and TryStrToQWord('$' + AnsiRightStr(aValue, Length(aValue)-2), c); + ptPreset: + result := (aValue = fName); + ptString: + result := true; + end; + if result then + result := inherited SetValue(aValue); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameterSingle////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterSingle.GetString(const aOptions: TutlParameterStringFlags): String; +begin + result := fName; + case fType of + ptBoolean: + result := result+':b'; + ptHex: + result := result+':h'; + ptInteger: + result := result+':i'; + ptString: + result := result+':s'; + ptPreset: + result := '''' + result + ''''; + end; + if (psfBrackets in aOptions) then begin + if fOptional then + result := '(' + result + ')' + else + result := '[' + result + ']'; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMenuParameterSingle.Create(const aOptional: Boolean; + const aName, aDescription: String; const aType: TutlParameterType); +begin + inherited Create(aOptional, aName, aDescription); + fType := aType; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMenuParameterSingle.Destroy; +begin + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuParameterGroup/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterGroup.GetCount: Integer; +begin + result := fParameters.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterGroup.GetParameter(const aIndex: Integer): TutlMenuParameterSingle; +begin + if (aIndex >= 0) and (aIndex < fParameters.Count) then + result := fParameters[aIndex] + else + raise Exception.Create(format('TMenuParameterGroup.GetParameter - index out of bounds (%d)', [aIndex])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterGroup.WriteConfig(const aMCF: TutlMCFSection); +var + i: Integer; +begin + inherited WriteConfig(aMCF); + with aMCF.Sections['parameters'] do begin + SetInt('count', fParameters.Count); + for i := 0 to fParameters.Count-1 do + fParameters[i].WriteConfig(Sections[IntToStr(i)]); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterGroup.ReadConfig(const aMCF: TutlMCFSection); +var + c, i: Integer; +begin + inherited ReadConfig(aMCF); + with aMCF.Sections['parameters'] do begin + c := GetInt('count', 0); + for i := 0 to c-1 do + AddParameter('', '', ptPreset).ReadConfig(Sections[IntToStr(i)]); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterGroup.GetAutoCompleteStrings(const aStrings: TStrings); +var + i: Integer; +begin + for i := 0 to fParameters.Count-1 do + fParameters[i].GetAutoCompleteStrings(aStrings); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterGroup.SetValue(const aValue: String): Boolean; +var + i: Integer; +begin + for i := 0 to fParameters.Count-1 do + if fParameters[i].SetValue(aValue) then begin + result := inherited SetValue(aValue); + break; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterGroup.GetString(const aOptions: TutlParameterStringFlags): String; +var + i: Integer; + s: String; +begin + result := ''; + for i := 0 to fParameters.Count-1 do begin + if result <> '' then + result := result + '|'; + s := fParameters[i].GetString; + s := copy(s, 2, Length(s)-2); + result := result + s; + end; + result := fName + ':' + result; + if (psfBrackets in aOptions) then begin + if (fOptional) then + result := '(' + result + ')' + else + result := '[' + result + ']'; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterGroup.AddParameter(const aName, aDescription: String; const aType: TutlParameterType): TutlMenuParameterSingle; +begin + result := TutlMenuParameterSingle.Create(false, aName, aDescription, aType); + fParameters.Add(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuParameterGroup.AddParameter(const aParameter: TutlMenuParameterSingle): TutlMenuParameterSingle; +begin + result := aParameter; + fParameters.Add(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuParameterGroup.DelParameter(const aIndex: Integer); +begin + if (aIndex >= 0) and (aIndex < fParameters.Count) then + fParameters.Delete(aIndex) + else + raise Exception.Create(format('TMenuParameterGroup.DelParameter - index out of bounds (%d)', [aIndex])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMenuParameterGroup.Create(const aOptional: Boolean; const aName, aDescription: String); +begin + inherited Create(aOptional, aName, aDescription); + fParameters := TutlMenuParameterSingleList.Create(true); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMenuParameterGroup.Destroy; +begin + FreeAndNil(fParameters); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMenuItem///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetCount: Integer; +begin + result := fItems.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetItems(const aIndex: Integer): TutlMenuItem; +begin + if (aIndex >= 0) and (aIndex < fItems.Count) then + result := fItems[aIndex] + else + raise Exception.Create(format('TMenuItem.GetItems - index out of bounds (%d)', [aIndex])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetMenuPath: String; +var + m: TutlMenuItem; +begin + result := ''; + m := self; + while Assigned(m) do begin + if Length(result) > 0 then + result := ' ' + result; + result := m.GetString + result; //m.Command + result; + if (m <> m.Parent) then + m := m.Parent + else + m := nil; + end; + result := Trim(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetParamCount: Integer; +begin + result := fParameters.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetParameters(const aIndex: Integer): TutlMenuParameter; +begin + if (aIndex >= 0) and (aIndex < fParameters.Count) then + result := fParameters[aIndex] + else + raise Exception.Create(format('TMenuItem.GetParameters - index out of bounds', [aIndex])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetParameterString: String; +var + i: Integer; +begin + result := ''; + for i := 0 to fParameters.Count-1 do begin + if result <> '' then + result := result + ' '; + result := result + fParameters[i].GetString; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.SetCallback(aValue: TutlCallback); +begin + if fCallback = aValue then + exit; + fCallback := aValue; + fExecutable := Assigned(fCallback); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.WriteConfig(const aMCF: TutlMCFSection); +var + i: Integer; +begin + with aMCF do begin + SetString('command', fCommand); + SetString('description', fDescription); + SetBool ('executable', fExecutable); + with Sections['parameters'] do begin + SetInt('count', fParameters.Count); + for i := 0 to fParameters.Count-1 do + fParameters[i].WriteConfig(Sections[IntToStr(i)]); + end; + with Sections['menus'] do begin + SetInt('count', fItems.Count); + for i := 0 to fItems.Count-1 do + fItems[i].WriteConfig(Sections[IntToStr(i)]); + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.ReadConfig(const aMCF: TutlMCFSection); +var + c, i: Integer; + s: String; + p: TutlMenuParameter; +begin + fItems.Clear; + fParameters.Clear; + with aMCF do begin + fCommand := GetString('command', ''); + fDescription := GetString('description', ''); + fExecutable := GetBool ('executable', false); + with Sections['parameters'] do begin + c := GetInt('count', 0); + for i := 0 to c-1 do begin + s := Sections[IntToStr(i)].GetString('classname', ''); + p := nil; + if (s = TutlMenuParameterSingle.ClassName) then + p := TutlMenuParameterSingle.Create(true, '', '', ptPreset) + else if (s = TutlMenuParameterGroup.ClassName) then + p := TutlMenuParameterGroup.Create(true, '', ''); + if Assigned(p) then begin + fParameters.Add(p); + p.ReadConfig(Sections[IntToStr(i)]); + end; + end; + end; + with Sections['menus'] do begin + c := GetInt('count', 0); + for i := 0 to c-1 do + AddItem('', '', nil).ReadConfig(Sections[IntToStr(i)]); + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.GetAutoCompleteStrings(const aList: TStrings; const aParameter: TutlMenuParameterList); +var + hasAllParam: Boolean; + i: Integer; + p: TutlMenuParameter; +begin + hasAllParam := true; + for i := 0 to fParameters.Count-1 do begin + p := fParameters[i]; + if not p.Optional then begin + if not aParameter.HasParameter(p.Name) then begin + p.GetAutoCompleteStrings(aList); + hasAllParam := false; + break; + end; + end else begin + if not aParameter.HasParameter(p.Name) then + p.GetAutoCompleteStrings(aList); + end; + end; + if hasAllParam then begin + for i := 0 to fItems.Count-1 do + aList.Add(fItems[i].Command); + if Command <> 'help' then + aList.Add('help'); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.GetString: String; +var + s: String; +begin + result := Command; + s := ParameterString; + if (Command <> '') and (s <> '') then + result := result + ' '; + result := result + s; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.AddItem(const aCmd, aDesc: String; const aCallback: TutlCallback): TutlMenuItem; +begin + result := TutlMenuItem.Create(self, aCmd, aDesc, aCallback); + fItems.Add(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.AddItem(const aItem: TutlMenuItem): TutlMenuItem; +begin + result := aItem; + fItems.Add(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.DelItem(const aIndex: Integer); +begin + if (aIndex >= 0) and (aIndex < fItems.Count) then + fItems.Delete(aIndex) + else + raise Exception.Create(format('TMenuItem.DelItem - index out of bounds (%d)', [aIndex])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMenuItem.AddParameter(const aParameter: TutlMenuParameter): TutlMenuParameter; +begin + result := aParameter; + result.fParent := self; + fParameters.Add(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.DelParameter(const aIndex: Integer); +begin + if (aIndex >= 0) and (aIndex < fParameters.Count) then + fParameters.Delete(aIndex) + else + raise Exception.Create(format('TMenuItem.DelParameter - index out of bounds (%d)', [aIndex])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMenuItem.Create(const aParent: TutlMenuItem; const aCmd, aDesc: String; const aCallback: TutlCallback); +begin + inherited Create; + fParent := aParent; + fCommand := aCmd; + fDescription := aDesc; + fCallback := aCallback; + fExecutable := Assigned(fCallback); + fItems := TutlMenuItemList.Create(true); + fParameters := TutlMenuParameterList.Create(true); + + if aCmd <> 'help' then begin + fHelpItem := TutlMenuItem.Create(self, 'help', 'shows the help. use ''all'' to display the menu tree. use ''detail'' to display command details', nil); + with fHelpItem.AddParameter(TutlMenuParameterGroup.Create(true, 'mode', 'specify how to display the help menu')) as TutlMenuParameterGroup do begin + AddParameter('all', 'displays the complete menu tree', ptPreset); + AddParameter('detail', 'displays detailed information', ptPreset); + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMenuItem.Destroy; +begin + FreeAndNil(fItems); + FreeAndNil(fParameters); + FreeAndNil(fHelpItem); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCommandMenu////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCommandMenu.GetCmdParameter: TutlMenuParameterList; +begin + result := fCmdParameter; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.SplitCmdString(const aText: String; const aChar: Char); +var + i: Integer; + Buffer: String; + Quote: Boolean; +begin + fCmdStack.Clear; + Buffer := ''; + Quote := false; + for i := 1 to Length(aText) do begin + if (aText[i] = aChar) and not Quote then begin + Buffer := Trim(Buffer); + fCmdStack.Add(Buffer); + Buffer := ''; + end else if (aText[i] = '"') then + Quote := not Quote + else + Buffer := Buffer + aText[i]; + end; + if Buffer <> '' then begin + Buffer := Trim(Buffer); + fCmdStack.Add(Buffer); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCommandMenu.ParseCommand: TutlParseResult; + + procedure RestoreParameterStack(const aMenu: TutlMenuItem); + var + p: TutlMenuParameter; + begin + p := fCmdParameter.Seek; + while Assigned(p) and (p.Parent = aMenu) do begin + fCmdStack.Push(p.Value); + fCmdParameter.Pop; + p := fCmdParameter.Seek; + end; + end; + + function BackTrack(const aMenu: TutlMenuItem): TutlParseResult; + var + s, cmd: String; + OptionalParamCount: Integer; + i, c: Integer; + m: TutlMenuItem; + p: TutlMenuParameter; + begin + //Stack is empty + if (fCmdStack.Count = 0) then begin + if Assigned(aMenu.Callback) or (aMenu = fHelpItem) then + result := prSuccess + else + result := prIncompleteCmd; + fCurrentMenu := aMenu; + exit; + end; + s := fCmdStack.Pop; + cmd := LowerCase(s); + + //find command + m := nil; + for i := 0 to aMenu.Count-1 do + if (LowerCase(aMenu[i].Command) = cmd) then begin + m := aMenu[i]; + break; + end; + if not Assigned(m) and (cmd = 'help') then begin + m := fHelpItem; + m.fParent := aMenu; + end; + + if Assigned(m) then begin + //count optional parameters + c := 0; + for i := 0 to m.ParamCount-1 do + if (m.Parameters[i].Optional) then + inc(c); + OptionalParamCount := (1 shl c) - 1; + + //backtrack optional parameters + while (OptionalParamCount >= 0) do begin + result := prSuccess; + fCurrentParamCount := 0; + c := 0; + for i := 0 to m.ParamCount-1 do begin + p := m.Parameters[i]; + if not p.Optional or (((OptionalParamCount shr c) and 1) = 1) then begin + if (fCmdStack.Count <= 0) then begin + result := prInvalidParamCount; + RestoreParameterStack(m); + fCurrentMenu := m; + break; + end else if (LowerCase(fCmdStack.Seek) = 'help') then begin + break; + end else if not (p.SetValue(fCmdStack.Seek)) then begin + result := prInvalidParam; + RestoreParameterStack(m); + fCurrentMenu := m; + break; + end else begin + inc(fCurrentParamCount); + fCmdParameter.Push(p); + fCmdStack.Pop; + end; + if p.Optional then + inc(c); + end; + end; + + if (result = prSuccess) then begin + result := BackTrack(m); + if result = prUnknownCommand then + fCurrentMenu := aMenu; + end; + if result <> prSuccess then + dec(OptionalParamCount) + else + OptionalParamCount := -1; + end; + end else begin + fCmdStack.Push(s); + fUnknownCmd := s; + result := prUnknownCommand; + end; + end; + +begin + fCmdParameter.Clear; + fCurrentParamCount := 0; + fInvalidParam := ''; + fUnknownCmd := ''; + fCurrentMenu := nil; + result := BackTrack(self); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.ExecuteCommand(const aCmd: String); +var + r: TutlParseResult; + p: TutlMenuParameter; +begin + fLastCmd := aCmd; + SplitCmdString(aCmd, ' '); + r := ParseCommand; + case r of + prSuccess: + if (fCurrentMenu = fHelpItem) then begin + p := nil; + if (fCmdParameter.Count > 0) and (fCmdParameter[fCmdParameter.Count-1].Parent = fHelpItem) then + p := fCmdParameter[fCmdParameter.Count-1]; + if not Assigned(p) then + DisplayHelp(fHelpItem.Parent) + else if (p.Value = 'all') then + DisplayHelp(fHelpItem.Parent, hoAll) + else if (p.Value = 'detail') then + DisplayHelp(fHelpItem.Parent, hoDetail) + else + DisplayHelp(fHelpItem.Parent); + end else + fCurrentMenu.Callback(self); + prInvalidParam: + DisplayInvalidParam(fInvalidParam); + prInvalidParamCount: + DisplayInvalidParamCount(fCurrentParamCount); + prIncompleteCmd: + DisplayIncompleteCommand; + prUnknownCommand: + DisplayUnknownCommand(fUnknownCmd); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.DisplayHelp(const aRefMenu: TutlMenuItem; const aOption: TutlHelpOption); +var + maxLength: Integer; + + function AddStr(const aOld, aNew, aPostfix: String): String; + begin + result := aOld; + if (aOld <> '') and (aNew <> '') then + result := result + aPostfix; + result := result + aNew; + end; + + function GetMaxLength(const aItem: TutlMenuItem): Integer; + var + i, l: Integer; + m: TutlMenuItem; + begin + result := 0; + for i := 0 to aItem.Count-1 do begin + m := aItem[i]; + l := Length(m.GetString); + if l > result then + result := l; + if (aOption = hoAll) then begin + l := GetMaxLength(m) + 2; + if l > result then + result := l; + end; + end; + end; + + function FillStr(const aStr: String; const aChar: Char; const aLength: Integer): String; + begin + result := aStr + StringOfChar(aChar, aLength-Length(aStr)); + end; + + procedure AddHelpText(var aMsg: String; const aPrefix: String; const aItem: TutlMenuItem); + begin + if (aMsg <> '') then + aMsg := aMsg + sLineBreak; + aMsg := aMsg + FillStr(aPrefix + aItem.GetString, ' ', maxLength) + ' ' + aItem.Description; + end; + + procedure AddHelpItems(var aMsg: String; const aPrefix: String; const aItem: TutlMenuItem; const aDisplayHelpItem: Boolean = false); + var + i: Integer; + m: TutlMenuItem; + begin + for i := 0 to aItem.Count-1 do begin + m := aItem[i]; + AddHelpText(aMsg, aPrefix, m); + if (aOption = hoAll) then + AddHelpItems(aMsg, aPrefix+' ', m); + end; + if aDisplayHelpItem then + AddHelpText(aMsg, aPrefix, fHelpItem); + end; + + procedure DisplayGroupParameters(var aMsg: String; const aParamGroup: TutlMenuParameterGroup); + var + i, maxLen, l: Integer; + p: TutlMenuParameter; + begin + maxLen := 0; + for i := 0 to aParamGroup.Count-1 do begin + l := Length(aParamGroup[i].GetString([])); + if l > maxLen then + maxLen := l; + end; + inc(maxLen, 3); + + for i := 0 to aParamGroup.Count-1 do begin + p := aParamGroup[i]; + aMsg := aMsg + sLineBreak + ' ' + + FillStr(p.GetString([]), ' ', maxLen) + + p.Description; + end; + end; + + procedure DisplayParameters(var aMsg: String); + var + i, maxLen, l: Integer; + p: TutlMenuParameter; + begin + maxLen := 0; + for i := 0 to aRefMenu.ParamCount-1 do begin + l := Length(aRefMenu.Parameters[i].GetString); + if (l > maxLen) then + maxLen := l; + end; + inc(maxLen, 3); + + aMsg := aMsg + sLineBreak + 'Parameters:'; + if (aRefMenu.ParamCount > 0) then begin + for i := 0 to aRefMenu.ParamCount-1 do begin + p := aRefMenu.Parameters[i]; + aMsg := aMsg + sLineBreak + ' ' + FillStr(p.GetString, ' ', maxLen); + if p.Optional then + aMsg := aMsg + '(optional) ' + else + aMsg := aMsg + ' '; + aMsg := aMsg + p.Description; + if (p is TutlMenuParameterGroup) then + DisplayGroupParameters(aMsg, p as TutlMenuParameterGroup); + end; + end else + aMsg := aMsg + sLineBreak + ' [no Parameters]'; + end; + +var + menu: TutlMenuItem; + msg: String; +begin + if Assigned(aRefMenu) then + menu := aRefMenu + else + menu := self; + + msg := AddStr(menu.MenuPath, menu.ParameterString, ' '); + msg := AddStr(msg, menu.Description, ' - '); + fHelpItem.fParent := nil; + if (aOption = hoDetail) then + msg := msg + sLineBreak + 'Submenus / Commands:'; + maxLength := max(GetMaxLength(menu), Length(fHelpItem.GetString)) + 2; + if (msg = '') then + msg := sLineBreak; + AddHelpItems(msg, ' ', menu, true); + if (aRefMenu is TutlConsoleMenu) then + AddHelpText(msg, ' ', (self as TutlConsoleMenu).fExitMenu); + if (aOption = hoDetail) then + DisplayParameters(msg); + utlLogger.Log(Self, msg, []); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.DisplayIncompleteCommand; + + function GetMenuPath: String; + var + s: String; + begin + s := ''; + if Assigned(fCurrentMenu) then + s := fCurrentMenu.MenuPath; + if (s <> '') then + result := s+' ' + else + result := ''; + end; + +begin + utlLogger.Error(Self, 'incomplete command! type "%shelp" to get further information.', [GetMenuPath]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.DisplayUnknownCommand(const aCmd: String); + + function GetMenuPath: String; + var + s: String; + begin + s := ''; + if Assigned(fCurrentMenu) then + s := fCurrentMenu.MenuPath; + if (s <> '') then + result := s+' ' + else + result := ''; + end; + + function GetCommand: String; + begin + if (aCmd <> '') then + result := ' "'+aCmd+'"' + else + result := ''; + end; + +begin + utlLogger.Error(Self, 'unknown command%s! type "%shelp" to get further information.', [GetCommand, GetMenuPath]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.DisplayInvalidParamCount(const aParamCount: Integer); + + function GetMenuPath: String; + var + s: String; + begin + s := ''; + if Assigned(fCurrentMenu) then + s := fCurrentMenu.MenuPath; + if (s <> '') then + result := s+' ' + else + result := ''; + end; + + function GetParamCount: String; + var + i, c: Integer; + begin + result := ' ('; + if (aParamCount >= 0) then begin + result := result + IntToStr(aParamCount); + end; + if Assigned(fCurrentMenu) then begin + c := 0; + for i := 0 to fCurrentMenu.ParamCount-1 do + if not fCurrentMenu.Parameters[i].Optional then + inc(c); + result := result + ', expected '+IntToStr(c); + end; + result := result + ')'; + if (result = ' ()') then + result := ''; + end; + +begin + utlLogger.Log(Self, 'invalid parameter count%s! type "%shelp" to get further information.', [GetParamCount, GetMenuPath]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandMenu.DisplayInvalidParam(const aParam: String); + + function GetMenuPath: String; + var + s: String; + begin + s := ''; + if Assigned(fCurrentMenu) then + s := fCurrentMenu.MenuPath; + if (s <> '') then + result := s+' ' + else + result := ''; + end; + + function GetParam: String; + begin + if (aParam <> '') then + result := ' "'+aParam+'"' + else + result := ''; + end; + +begin + utlLogger.Log(Self, 'invalid parameter%s! type "%shelp" to get further information.', [GetParam, GetMenuPath]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.LoadFromStream(const aStream: TStream); +var + mcf: TutlMCFFile; +begin + mcf := TutlMCFFile.Create(nil); + try + mcf.LoadFromStream(aStream); + ReadConfig(mcf); + finally + mcf.Free; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMenuItem.SaveToStream(const aStream: TStream); +var + mcf: TutlMCFFile; +begin + mcf := TutlMCFFile.Create(nil); + try + WriteConfig(mcf); + mcf.SaveToStream(aStream); + finally + mcf.Free; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCommandMenu.Create(const aHelp: String); +begin + inherited Create(nil, '', aHelp, nil); + fCmdStack := TutlStringStack.Create; + fCmdParameter := TutlMenuParameterStack.Create(False); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlCommandMenu.Destroy; +begin + FreeAndNil(fCmdStack); + FreeAndNil(fCmdParameter); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCommandPrompt///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.SetPrefix(aValue: String); +begin + if fPrefix = aValue then + exit; + DelInput(true); + delete(fCurrent, 1, Length(fPrefix)); + if fHistoryID >= 0 then + delete(fHistoryBackup, 1, Length(fPrefix)); + fPrefix := aValue; + fStartIndex := Length(fPrefix)+1; + fCurrent := fPrefix + fCurrent; + if fHistoryID >= 0 then + fHistoryBackup := fPrefix + fHistoryBackup; + RestoreInput; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCommandPrompt.GetCurrent: String; +begin + if fHistoryID = -1 then + result := copy(fCurrent, fStartIndex, MaxInt) + else + result := copy(fHistoryBackup, fStartIndex, MaxInt); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.SetCurrent(aValue: String); +begin + DelInput; + fCurrent := fPrefix + aValue; + fHistoryID := -1; + RestoreInput; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.SetHiddenChar(aValue: Char); +begin + if fHiddenChar = aValue then + exit; + DelInput(true); + fHiddenChar := aValue; + RestoreInput; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.CursorToStart; +begin + Write(StringOfChar(#8, fCurID-fStartIndex)); + fCurID := fStartIndex; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.CursorToEnd; +begin + Write(Copy(fCurrent, fCurID, MaxInt)); + fCurID := Length(fCurrent)+1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.DelInput(const aAll: Boolean); +var + l: Integer; +begin + if not aAll then + l := fCurID - fStartIndex + else + l := fCurID - 1; + Write(StringOfChar(#08, l)); + Write(StringOfChar(' ', l)); + Write(StringOfChar(#08, l)); + dec(fCurID, l); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.RestoreInput; +begin + if (fHiddenChar <> #0) then begin + while fCurID < fStartIndex do begin + Write(fCurrent[fCurID]); + inc(fCurID); + end; + Write(StringOfChar(fHiddenChar, Length(fCurrent) - fCurID + 1)) + end else + Write(Copy(fCurrent, fCurID, MaxInt)); + fCurID := Length(fCurrent)+1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.CursorRight; +begin + if fCurID <= Length(fCurrent) then begin + if (fHiddenChar <> #0) then + Write(fHiddenChar) + else + Write(fCurrent[fCurID]); + inc(fCurID); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.CursorLeft; +begin + if fCurID > fStartIndex then begin + Write(#8); + dec(fCurID, 1); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.DelChar(const aBeforeCursor: Boolean); +begin + if not aBeforeCursor and (fCurID <= Length(fCurrent)) then begin + Delete(fCurrent, fCurID, 1); + Write(copy(fCurrent, fCurID, MaxInt), ' '); + Write(StringOfChar(#8, Length(fCurrent)-fCurID+2)); + end else if aBeforeCursor and (fCurID > fStartIndex) then begin + Delete(fCurrent, fCurID-1, 1); + Write(#8, copy(fCurrent, fCurID-1, MaxInt), ' '); + Write(StringOfChar(#8, Length(fCurrent)-fCurID+3)); + dec(fCurID); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.WriteChar(const c: Char); +begin + if (fCurID <= Length(fCurrent)) then begin + Write('#', copy(fCurrent, fCurID, MaxInt)); + Write(StringOfChar(#8, Length(fCurrent)-fCurID+2)); + end; + Insert(c, fCurrent, fCurID); + inc(fCurID, 1); + if (fHiddenChar <> #0) then + Write(fHiddenChar) + else + Write(c); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCommandPrompt.ReadLnEx: String; +var + c: Byte; + key: Char; + CtrlKey: Boolean; + s: String; + tabPressed: Boolean; +begin + fHistoryID := -1; + CtrlKey := false; + tabPressed := false; + + fConsoleCS.Enter; + try + DelInput(true); + RestoreInput; + finally + fConsoleCS.Leave; + end; + + while fRunning do begin + key := ReadKey; + c := Ord(key) and $FF; + fConsoleCS.Enter; + try + if (CtrlKey) then begin + CtrlKey := false; + case c of + 72: begin //KEY_UP + if (fHistoryID+1 < fHistory.Count) then begin + if (fHistoryID = -1) then + fHistoryBackup := fCurrent; + CursorToEnd; + DelInput; + inc(fHistoryID); + fCurrent := fPrefix + fHistory[fHistoryID]; + RestoreInput; + end; + end; + 80: begin //KEY_DOWN + if (fHistoryID >= 0) then begin + CursorToEnd; + DelInput; + dec(fHistoryID); + if (fHistoryID >= 0) then + fCurrent := fPrefix + fHistory[fHistoryID] + else + fCurrent := fHistoryBackup; + RestoreInput; + end; + end; + 75: begin //KEY_LEFT + CursorLeft; + end; + 77: begin //KEY_RIGHT + CursorRight; + end; + 82: begin //KEY_INSERT + end; + 83: begin //KEY_DELETE + DelChar(false); + end; + 71: begin //KEY_HOME + CursorToStart; + end; + 79: begin //KEY_END + CursorToEnd; + end; + 73: begin //KEY_PGUP + end; + 81: begin //KEY_PGDOWN + end; + end; + end else begin + if (tabPressed) then + tabPressed := (c = VK_TAB); + case c of + VK_UNKNOWN: begin + CtrlKey := true; + end; + VK_BACK: begin + DelChar(true); + end; + VK_TAB: begin + CursorToEnd; + DelInput; + s := fCurrent; + fCurrent := fPrefix + + AutoComplete(copy(fCurrent, fStartIndex, MaxInt), tabPressed); + RestoreInput; + if s <> fCurrent then + tabPressed := false + else + tabPressed := not tabPressed; + end; + VK_RETURN: begin //RETURN + break; + end; + VK_ESCAPE: begin + Reset; + end; + else + WriteChar(Chr(c)); + end; + end; + finally + fConsoleCS.Leave; + end; + end; + + fConsoleCS.Enter; + try + WriteLn; + finally + fConsoleCS.Leave; + end; + result := copy(fCurrent, Length(fPrefix)+1, MaxInt); + Reset; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCommandPrompt.AutoComplete(const aInput: String; const aDisplayPossibilities: Boolean): String; +begin + result := aInput; + if Assigned(fOnAutoComplete) then + result := fOnAutoComplete(self, aInput, aDisplayPossibilities); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.AddHistory(const aInput: String); +begin + if fHistoryEnabled and (fHiddenChar = #0) and + ((fHistory.Count <= 0) or (fHistory[0] <> aInput)) then + fHistory.Insert(0, aInput); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.DoInput; +begin + if Assigned(fOnInput) then + fOnInput(self, fInput); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.Start; +begin + Reset; + fRunning := true; + while fRunning do begin + fInput := ReadLnEx; + AddHistory(fInput); + DoInput; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.Stop; +begin + fRunning := false; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.Reset; +begin + CursorToEnd; + DelInput(true); + fCurrent := fPrefix; + fCurID := 1; + Restore; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.Clear; +begin + fConsoleCS.Enter; + try + DelInput(true); + finally + fConsoleCS.Leave; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCommandPrompt.Restore; +begin + fConsoleCS.Enter; + try + RestoreInput; + finally + fConsoleCS.Leave; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCommandPrompt.Create(const aConsoleCS: syncobjs.TCriticalSection); +begin + inherited Create; + fConsoleCS := aConsoleCS; + fPrefix := COMMAND_PROMPT_PREFIX; + fHistory := TStringList.Create; + fHistoryEnabled := true; + fStartIndex := Length(fPrefix)+1; +end; + +destructor TutlCommandPrompt.Destroy; +begin + FreeAndNil(fHistory); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlConsoleMenu////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlConsoleMenu.CommandInput(aSender: TObject; const aCmd: String); +begin + if fIsAsking then begin + fIsAsking := false; + fCommandPrompt.Current := fInputBackup; + fCommandPrompt.Prefix := COMMAND_PROMPT_PREFIX; + fCommandPrompt.OnAutoComplete := @AutoComplete; + fCommandPrompt.HiddenChar := #0; + fCommandPrompt.HistoryEnabled := true; + DoAnswer(aCmd); + end else begin + utlLogger.Debug(Self, 'CMD: ' + aCmd, []); + ExecuteCommand(aCmd); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlConsoleMenu.AutoComplete(aSender: TObject; const aCmd: String; + const aDisplayPossibilities: Boolean): String; +var + r: TutlParseResult; + s, cmd: String; + c: Char; + cmdList: TStringList; + i, CharIndex, MaxLength: Integer; + + function TestParam(const aParam: String): Boolean; + var + i: Integer; + c: QWord; + begin + if aParam = '[integer]' then + result := TryStrToInt(s, i) + else if aParam = '[hex]' then + result := AnsiStartsStr('0x', s) and TryStrToQWord('$' + copy(s, 3, MaxInt), c) + else + result := true; + end; + + function NextChar: Char; + var + i: Integer; + c: String; + begin + result := #0; + try + for i := 0 to cmdList.Count-1 do begin + c := cmdList[i]; + if (Length(c) > 0) and (c[1] <> '[') then begin + if (Length(c) >= CharIndex) then begin + if (result = #0) then + result := c[CharIndex] + else if (result <> c[CharIndex]) then begin + result := #0; + exit; + end; + end; + end; + end; + finally + inc(CharIndex); + end; + end; + + function GetMaxLength: Integer; + var + i, l: Integer; + begin + result := 0; + for i := 0 to cmdList.Count-1 do begin + l := Length(cmdList[i]); + if l > result then + result := l; + end; + end; + +begin + SplitCmdString(aCmd, ' '); + s := ''; + if (Length(aCmd) > 0) and (aCmd[Length(aCmd)] <> ' ') then begin + s := fCmdStack[fCmdStack.Count-1]; + fCmdStack.Delete(fCmdStack.Count-1); + end; + r := ParseCommand; + result := aCmd; + if (r in [prSuccess, prIncompleteCmd, prInvalidParamCount]) and Assigned(fCurrentMenu) then begin + cmdList := TStringList.Create; + try + fCurrentMenu.GetAutoCompleteStrings(cmdList, fCmdParameter); + if (s <> '') then begin + for i := cmdList.Count-1 downto 0 do begin + cmd := cmdList[i]; + if (Length(cmd) > 0) and (cmd[1] = '[') then begin + if not TestParam(cmd) then + cmdList.Delete(i); + end else if not AnsiStartsStr(s, cmd) then + cmdList.Delete(i); + end; + end; + + CharIndex := Length(s)+1; + c := NextChar; + while c <> #0 do begin + result := result + c; + c := NextChar; + end; + if (cmdList.Count = 1) then + result := result + ' '; + + if aDisplayPossibilities then begin + WriteLn(''); + s := ''; + MaxLength := GetMaxLength+5; + for i := 0 to cmdList.Count-1 do begin + cmd := cmdList[i]; + s := s + cmd + StringOfChar(' ', MaxLength-Length(cmd)); + if ((i+1) mod 5) = 0 then begin + writeln(s); + s := ''; + end; + end; + if (s <> '') then + WriteLn(s); + if (cmdList.Count = 0) then + WriteLn('[no possible commands]'); + end; + finally + cmdList.Free; + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlConsoleMenu.DoAnswer(const aInput: String); +begin + if Assigned(fOnAnswer) then + fOnAnswer(self, aInput); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlConsoleMenu.ExecuteCommand(const aCmd: String); +begin + if (LowerCase(AnsiLeftStr(trim(aCmd), 4)) = 'exit') then + ExitMenu + else + inherited ExecuteCommand(aCmd); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlConsoleMenu.StartMenu; +begin + fCommandPrompt.Start; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlConsoleMenu.ExitMenu; +begin + fCommandPrompt.Stop; +end; + +procedure TutlConsoleMenu.Ask(const aQuestion: String; const aHidden: Boolean; const aOnAnswer: TutlInputEvent); +begin + if Assigned(aOnAnswer) then + fOnAnswer := aOnAnswer; + fIsAsking := true; + fInputBackup := fCommandPrompt.Current; + if aHidden then + fCommandPrompt.HiddenChar := '*' + else + fCommandPrompt.HiddenChar := #0; + fCommandPrompt.HistoryEnabled := false; + fCommandPrompt.OnAutoComplete := nil; + fCommandPrompt.Prefix := aQuestion; + fCommandPrompt.Current := ''; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlConsoleMenu.Create(const aHelp: String; const aConsoleCS: syncobjs.TCriticalSection); +begin + inherited Create(aHelp); + fExitMenu := TutlMenuItem.Create(self, 'exit', 'exit programm', nil); + fCommandPrompt := TutlCommandPrompt.Create(aConsoleCS); + fCommandPrompt.OnAutoComplete := @AutoComplete; + fCommandPrompt.OnInput := @CommandInput; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlConsoleMenu.Destroy; +begin + FreeAndNil(fCommandPrompt); + FreeAndNil(fExitMenu); + inherited Destroy; +end; + +end. + diff --git a/uutlConversion.pas b/uutlConversion.pas new file mode 100644 index 0000000..b30c8b5 --- /dev/null +++ b/uutlConversion.pas @@ -0,0 +1,66 @@ +unit uutlConversion; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit stellt Methoden für Konvertierung verschiedener Datentypen zur Verfügung } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +function Supports(const aInstance: TObject; const aClass: TClass; out aObj): Boolean; overload; +function HexToBinary(HexValue: PChar; BinValue: PByte; BinBufSize: Integer): Integer; + +implementation + +function Supports(const aInstance: TObject; const aClass: TClass; out aObj): Boolean; +begin + result := Assigned(aInstance) and aInstance.InheritsFrom(aClass); + if result then + TObject(aObj) := aInstance + else + TObject(aObj) := nil; +end; + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//wandelt einen Hex-String in einen Blob um +//@hexvalue: Hex-String +//@binvalue: Zeiger auf einen Speicherbereich +//@binbufsize: maximale größe die geschrieben werden darf +//@result: gelesene bytes +function HexToBinary(HexValue: PChar; BinValue: PByte; BinBufSize: Integer): Integer; +var i,j,h,l : integer; + +begin + i:=binbufsize; + while (i>0) do + begin + if hexvalue^ IN ['A'..'F','a'..'f'] then + h:=((ord(hexvalue^)+9) and 15) + else if hexvalue^ IN ['0'..'9'] then + h:=((ord(hexvalue^)) and 15) + else + break; + inc(hexvalue); + if hexvalue^ IN ['A'..'F','a'..'f'] then + l:=(ord(hexvalue^)+9) and 15 + else if hexvalue^ IN ['0'..'9'] then + l:=(ord(hexvalue^)) and 15 + else + break; + j := l + (h shl 4); + inc(hexvalue); + binvalue^:=j; + inc(binvalue); + dec(i); + end; + result:=binbufsize-i; +end; + + +end. + diff --git a/uutlEmbeddedProfiler.inc b/uutlEmbeddedProfiler.inc new file mode 100644 index 0000000..7d8a241 --- /dev/null +++ b/uutlEmbeddedProfiler.inc @@ -0,0 +1,45 @@ +{$IFNDEF PROFILER_DISABLE} + {.$DEFINE PROFILER_ENABLE} + {.$DEFINE PROFILER_DISABLE_NAMES} +{$ENDIF} + + +(****************************************************************************** + Usage: + Somewhere, use this: + {.$DEFINE PROFILER_DISABLE} //use this to disable profiling for specific unit + {$I uutlEmbeddedProfiler.inc} + (also add wherever that file is to the project's include search path -Fi, + unit search path is not enough) + In Uses-List: SysUtils, ... __PROFUSE; + (notice: no comma before __PROFUSE) + In Code: + begin + __PROFENTER + ... code here ... + __PROFLEAVE + end; + ******************************************************************************) +{$macro on} +{$IFDEF PROFILER_ENABLE} + {$DEFINE __PROFENTER:=uutlEmbeddedProfiler.ProfilerEnterProc(Get_pc_addr); try} + {$DEFINE __PROFLEAVE:=finally uutlEmbeddedProfiler.ProfilerLeaveProc; end;} + {$DEFINE __PROFUSE:=, uutlEmbeddedProfiler} + {$DEFINE __PROFUSEPREV:=uutlEmbeddedProfiler, } + + {$IFNDEF PROFILER_DISABLE_NAMES} + {$DEFINE __PROFSETNAME:=uutlEmbeddedProfiler.ProfilerEnterProc(Get_pc_addr,} + {$DEFINE __PROFENTERNAME:=); try} + {$ELSE} + {$DEFINE __PROFSETNAME:=//} + {$DEFINE __PROFENTERNAME:=__PROFENTER} + {$ENDIF} +{$ELSE} + {$DEFINE __PROFENTER:=} + {$DEFINE __PROFLEAVE:=} + {$DEFINE __PROFUSE:=} + {$DEFINE __PROFUSEPREV:=} + {$DEFINE __PROFSETNAME:=//} + {$DEFINE __PROFENTERNAME:=} +{$ENDIF} + \ No newline at end of file diff --git a/uutlEmbeddedProfiler.pas b/uutlEmbeddedProfiler.pas new file mode 100644 index 0000000..e802124 --- /dev/null +++ b/uutlEmbeddedProfiler.pas @@ -0,0 +1,301 @@ +unit uutlEmbeddedProfiler; + +{$mode objfpc}{$H+} +{$OPTIMIZATION ON} +{$OPTIMIZATION REGVAR} +{$OPTIMIZATION PEEPHOLE} +{$OPTIMIZATION CSE} +{$OPTIMIZATION ASMCSE} + +interface + +uses + SysUtils; + +var + ProfilerEnabled: boolean; + +procedure ProfilerEnterProc(const Addr: Pointer); +procedure ProfilerEnterProc(const Addr: Pointer; const aName: String); +procedure ProfilerLeaveProc; + +implementation + +{$I uutlEmbeddedProfiler.inc} + +{$IFDEF PROFILER_ENABLE} + +uses + Windows, lineinfo{%H-}, Classes, fgl, unFastFileStream; + +type + TWriterThread = class(TThread) + private type + TCacheEntry = record + Name, Src: ShortString; Line: integer; + end; + TCacheList = specialize TFPGMap<PtrUInt, TCacheEntry>; + private + fAddressCache: TCacheList; + fPF: Int64; + procedure SaveCurrentWrite; + public + constructor Create; + destructor Destroy; override; + procedure Execute; override; + end; + + PEventRecord = ^TEventRecord; + TEventRecord = packed record + Name: String; + Func: PtrUInt; + Thread: TThreadID; + When: Int64; + end; + + TProfileDataFile = class + public + constructor Create(const {%H-}aFileName: string); + procedure WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); virtual; abstract; + procedure WriteLeave(Thread: TThreadID; When: Int64); virtual; abstract; + end; + +{$DEFINE __HEAD} + //{$I uutlProfilerPlainText.inc} + {$I uutlProfilerPlainTextMMap.inc} +{$UnDef __HEAD} + //{$I uutlProfilerPlainText.inc} + {$I uutlProfilerPlainTextMMap.inc} + + +const + MAX_EVENT_COUNT = 1000; + RETURN_FUNCTION : PtrUInt = PtrUInt(-1); + +var + ProfilerDataFile: TProfileDataFile; + LineNumberComp: PtrUInt; + Events: array[0..MAX_EVENT_COUNT-1] of TEventRecord; + InsertPtr, WritePtr: Integer; + WriterThread: TWriterThread; + SLInsert: Cardinal; + +procedure InstallWriterThread; +begin + if not Assigned(WriterThread) then + WriterThread:= TWriterThread.Create; +end; + +procedure UninstallWriterThread; +begin + if Assigned(WriterThread) then begin + WriterThread.Terminate; + WriterThread.WaitFor; + FreeAndNil(WriterThread); + end; +end; + +procedure NextInsert; +begin + inc(InsertPtr); + if InsertPtr >= MAX_EVENT_COUNT then + InsertPtr:= 0; + // wait until writer cleared this element + While Events[InsertPtr].Func <> 0 do + ThreadSwitch; +end; + +procedure CalibrateLineNumberCompensation1(const Addr: PtrUInt); +begin + LineNumberComp:= Addr; +end; + +procedure CalibrateLineNumberCompensation; +label + mark; +begin + mark: + CalibrateLineNumberCompensation1({%H-}PtrUInt(Get_pc_addr)); + //measure out one CALL + LineNumberComp:= LineNumberComp - {%H-}PtrUInt(@mark); + //go somewhere into the stack prep before the call + inc(LineNumberComp); +end; + +procedure TestDebugInfoPresent; +var + f,s: ShortString; + l: LongInt; +begin + f:= ''; + s:= ''; + l:= 0; + if not GetLineInfo({%H-}PtrUInt(@TestDebugInfoPresent),f,s,l) then begin + raise Exception.Create('Profiler is enabled, but no suitable debug info could be found.'); + Halt(); + end; +end; + +procedure ProfilerEnterProc(const Addr: Pointer); +begin + ProfilerEnterProc(Addr, ''); +end; + +procedure ProfilerEnterProc(const Addr: Pointer; const aName: String); +var + f: PtrUInt; + tid: TThreadID; + er: PEventRecord; +begin + if not ProfilerEnabled then + exit; + tid:= GetCurrentThreadId; + InstallWriterThread; + repeat + System.InterlockedCompareExchange(SLInsert, tid, 0); + until SLInsert = tid; + try + // measure as late (close to measured code) as possible, but still write .Func last, because that's our lockvar + f:= {%H-}PtrUInt(addr) - LineNumberComp; + er:= @Events[InsertPtr]; + er^.Thread := tid; + er^.Name := aName; + QueryPerformanceCounter(er^.When); + er^.Func := f; + NextInsert; + finally + System.InterLockedExchange(SLInsert, 0); + end; +end; + +procedure ProfilerLeaveProc; +var + t: Int64; + tid: TThreadID; + er: PEventRecord; +begin + if not ProfilerEnabled then + exit; + QueryPerformanceCounter(t{%H-}); + tid:= GetCurrentThreadId; + repeat + System.InterlockedCompareExchange(SLInsert, tid, 0); + until SLInsert = tid; + try + // measure as early (close to measured code) as possible, but still write .Func last, because that's our lockvar + er := @Events[InsertPtr]; + er^.Thread := tid; + er^.When := t; + er^.Name := ''; + er^.Func := RETURN_FUNCTION; + NextInsert; + finally + System.InterLockedExchange(SLInsert, 0); + end; +end; + +{ TProfileDataFile } + +constructor TProfileDataFile.Create(const aFileName: string); +begin + inherited Create; +end; + +{ TWriterThread } + +constructor TWriterThread.Create; +begin + inherited Create(false); + fAddressCache:= TCacheList.Create; + fAddressCache.Sorted:= true; + QueryPerformanceFrequency(fPF); +end; + +destructor TWriterThread.Destroy; +begin + FreeAndNil(fAddressCache); + inherited Destroy; +end; + +procedure TWriterThread.Execute; +begin + while not Terminated do begin + while Events[WritePtr].Func<>0 do begin + SaveCurrentWrite; + inc(WritePtr); + if WritePtr >= MAX_EVENT_COUNT then + WritePtr:= 0; + end; + Sleep(1); + end; + //finish up remaining data, by now, writing is disabled + while Events[WritePtr].Func<>0 do begin + SaveCurrentWrite; + inc(WritePtr); + if WritePtr >= MAX_EVENT_COUNT then + WritePtr:= 0; + end; +end; + +procedure TWriterThread.SaveCurrentWrite; +var + ce: TCacheEntry; + i: integer; +begin + if Events[WritePtr].Func = 0 then + exit; + if Events[WritePtr].Func = RETURN_FUNCTION then + ProfilerDataFile.WriteLeave(Events[WritePtr].Thread, (Events[WritePtr].When * 1000 * 1000) div fPF) + else begin + i:= fAddressCache.IndexOf(Events[WritePtr].Func); + if i < 0 then begin + ce.Line:= 0; + ce.Src:= ''; + GetLineInfo(Events[WritePtr].Func,ce.Name,ce.Src,ce.Line); + if (ce.Name = '') then + ce.Name := Format('0x%.16x', [Events[WritePtr].Func]); + fAddressCache.Add(Events[WritePtr].Func, ce); + end else + ce:= fAddressCache.Data[i]; + if (Events[WritePtr].Name <> '') then + ce.Name := '[' + Events[WritePtr].Name + '] ' + ce.Name; + ProfilerDataFile.WriteEnter(Events[WritePtr].Thread, (Events[WritePtr].When * 1000 * 1000) div fPF, ce.Name, ce.Src, ce.Line); + end; + Events[WritePtr].Func:= 0; +end; + +{$ELSE} + +procedure ProfilerEnterProc(const Addr: Pointer); inline; +begin end; + +procedure ProfilerEnterProc(const Addr: Pointer; const aName: String); inline; +begin end; + +procedure ProfilerLeaveProc; inline; +begin end; + +{$ENDIF} + +initialization + {$IFDEF PROFILER_ENABLE} + ProfilerEnabled:= false; + InsertPtr:= 0; + WritePtr:= 0; + WriterThread:= nil; + CalibrateLineNumberCompensation; + TestDebugInfoPresent; + //ProfilerDataFile:= TProfilePlainText.Create(ChangeFileExt(ParamStr(0), '.profraw')); + //ProfilerDataFile:= TProfileBinary.Create(ChangeFileExt(ParamStr(0), '.profbin')); + ProfilerDataFile:= TProfilePlainTextMMap.Create(ChangeFileExt(ParamStr(0), '.profraw')); + ProfilerEnabled:= true; + {$ENDIF} + +finalization + {$IFDEF PROFILER_ENABLE} + ProfilerEnabled:= false; + UninstallWriterThread; + FreeAndNil(ProfilerDataFile); + {$ENDIF} +end. + diff --git a/uutlEnumHelper.inc b/uutlEnumHelper.inc new file mode 100644 index 0000000..de5f7dd --- /dev/null +++ b/uutlEnumHelper.inc @@ -0,0 +1,58 @@ +{$IF defined(__ENUM_INTERFACE)} + +type __ENUM_HELPER = class +private type + TValueArray = packed array[0..__ENUM_LENGTH-1] of __ENUM_TYPE; +public + class function {%H}ToString(const Value: __ENUM_TYPE): String; reintroduce; + class function TryToEnum(const Str: String; out Value: __ENUM_TYPE): boolean; overload; + class function ToEnum(const Str: String; const aDefault: __ENUM_TYPE): __ENUM_TYPE; overload; + class function ToEnum(const Str: String): __ENUM_TYPE; overload; + class function Values: TValueArray; +strict private + const TABLE: packed record + E: TValueArray; // array of values + N: AnsiString; // comma-separated string of names + end = + +{$ELSEIF defined (__ENUM_IMPLEMENTATION)} + +class function __ENUM_HELPER.ToString(const Value: __ENUM_TYPE): String; +var + i: integer; +begin + Result:= ''; + if LookupVal(@Value, @TABLE.E, sizeof(__ENUM_TYPE), length(TABLE.E), i) then + Result:= PickString(TABLE.N, i); +end; + +class function __ENUM_HELPER.ToEnum(const Str: String): __ENUM_TYPE; +begin + if not TryToEnum(Str, Result) then + raise EConvertErrorAlias.CreateFmt('"%s" is an invalid value',[Str]); +end; + +class function __ENUM_HELPER.ToEnum(const Str: String; const aDefault: __ENUM_TYPE): __ENUM_TYPE; +begin + if not TryToEnum(Str, Result) then + Result:= aDefault; +end; + +class function __ENUM_HELPER.TryToEnum(const Str: String; out Value: __ENUM_TYPE): boolean; +var + i: integer; +begin + Result:= LookupString(Str, TABLE.N, i); + if Result then + Value:= TABLE.E[i]; +end; + +class function __ENUM_HELPER.Values: TValueArray; +begin + Result:= TABLE.E; +end; + +{$ENDIF} +{$undef __ENUM_TYPE} +{$undef __ENUM_LENGTH} +{$undef __ENUM_HELPER} diff --git a/uutlEnumHelper.pas b/uutlEnumHelper.pas new file mode 100644 index 0000000..f19a010 --- /dev/null +++ b/uutlEnumHelper.pas @@ -0,0 +1,101 @@ +unit uutlEnumHelper; + +(* Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit stellt einen Mechanismus zur Verfügung, ohne viel Aufwand, + Helper Klassen für Enums zu implementieren + Verwendung: + {$MACRO ON} + + interface + {$define __ENUM_INTERFACE} + {$define __ENUM_HELPER:=TSomeEnumH}{$define __ENUM_TYPE:=TSomeEnum}{$define __ENUM_LENGTH:=4} + {$I uutlEnumHelper.inc}( + E: (enVal1, enVal2, enVal3, enVal4); + N: 'enVal1,enVal2,enVal3,enVal4'; + ); end; + //... mehr davon + {$undef __ENUM_INTERFACE} + + implementation + {$define __ENUM_IMPLEMENTATION} + {$define __ENUM_HELPER:=TSomeEnumH}{$define __ENUM_TYPE:=TSomeEnum}{$I uutlEnumHelper.inc} + {$undef __ENUM_IMPLEMENTATION} *) + +interface + +uses + SysUtils, StrUtils; + +type + EConvertErrorAlias = SysUtils.EConvertError; + +function LookupString(const aStr, aTable: String; out found: integer): boolean; +function PickString(const aTable: String; const aIndex: integer): string; +function LookupVal(const aVal: Pointer; const aPtr: Pointer; const aStep, aCount: PtrInt; out found: integer): boolean; + +implementation + +function LookupString(const aStr, aTable: String; out found: integer): boolean; +var + tbl: string; + i,p,k: integer; + t: string; +begin + Result:= false; + tbl:= aTable + ','; + t:= ''; + k:= 0; + i:= 1; + while i < Length(tbl) do begin + p:= PosEx(',',tbl,i); + t:= Trim(Copy(tbl, i, p-i)); + i:= p+1; + if CompareText(t, aStr)=0 then begin + found:= k; + Result:= true; + exit; + end else + inc(k); + end; +end; + +function PickString(const aTable: String; const aIndex: integer): string; +var + tbl: String; + k,i,p: integer; +begin + result:= ''; + tbl:= aTable + ','; + i:= 1; + k:= aIndex; + while (k>0) and (i>0) do begin + i:= PosEx(',',tbl, i) + 1; + dec(k); + end; + p:= PosEx(',',tbl, i); + if p<=0 then + Result:= '' + else + Result:= Trim(Copy(tbl, i, p-i)); +end; + + +function LookupVal(const aVal: Pointer; const aPtr: Pointer; const aStep, aCount: PtrInt; out found: integer): boolean; +var + pt: Pointer; + i: integer; +begin + Result:= false; + pt:= aPtr; + for i:= 0 to aCount-1 do begin + if CompareMem(pt, aVal, aStep) then begin + Result:= true; + found:= i; + exit; + end; + inc(pt, aStep); + end; +end; + +end. diff --git a/uutlEventManager.pas b/uutlEventManager.pas new file mode 100644 index 0000000..e7b1813 --- /dev/null +++ b/uutlEventManager.pas @@ -0,0 +1,712 @@ +unit uutlEventManager; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit verwaltet Events und verteilt diese an registrierte Programm-Teile } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, uutlGenerics, syncobjs, uutlTiming, Controls, Forms, uutlMessageThread, uutlMessages; + +type + TutlEventType = ( + MOUSE_DOWN = 10, + MOUSE_UP, + MOUSE_WHEEL_UP, + MOUSE_WHEEL_DOWN, + MOUSE_MOVE, + MOUSE_ENTER, + MOUSE_LEAVE, + MOUSE_CLICK, + MOUSE_DBL_CLICK, + + KEY_DOWN = 20, + KEY_REPEAT, + KEY_UP, + + WINDOW_RESIZE = 30, + WINDOW_ACTIVATE, + WINDOW_DEACTIVATE + ); + TutlEventTypes = set of TutlEventType; + + { TutlInputEvent } + + TutlInputEvent = class + protected + function CreateInstance: TutlInputEvent; virtual; + procedure Assign(const aEvent: TutlInputEvent); virtual; + public + Timestamp: QWord; + EventType: TutlEventType; + function Clone: TutlInputEvent; + constructor Create(aType: TutlEventType); + end; + TutlInputEventList = specialize TutlList<TutlInputEvent>; + + { TutlMouseEvent } + + TutlMouseEvent = class(TutlInputEvent) + protected + function CreateInstance: TutlInputEvent; override; + procedure Assign(const aEvent: TutlInputEvent); override; + public + Button: TMouseButton; + ClientPos, + ScreenPos: TPoint; + constructor Create(aType: TutlEventType; aButton: TMouseButton; aClientPos, aScreenPos: TPoint); + constructor Create(aType: TutlEventType; aClientPos, aScreenPos: TPoint); + end; + + { TutlKeyEvent } + + TutlKeyEvent = class(TutlInputEvent) + protected + function CreateInstance: TutlInputEvent; override; + procedure Assign(const aEvent: TutlInputEvent); override; + public + CharCode: WideChar; + KeyCode: Word; + constructor Create(aType: TutlEventType; aCharCode: WideChar; aKeyCode: Word); + end; + + { TutlWindowEvent } + + TutlWindowEvent = class(TutlInputEvent) + protected + function CreateInstance: TutlInputEvent; override; + procedure Assign(const aEvent: TutlInputEvent); override; + public + ScreenRect: TRect; + ClientWidth, + ClientHeight: Cardinal; + constructor Create(aType: TutlEventType; aScreenRect: TRect; aClientWidth, aClientHeight: Cardinal); + constructor Create(aType: TutlEventType; aScreenTopLeft: TPoint; aClientWidth, aClientHeight: Cardinal); + end; + + { TutlEventManager } + + TutlInputEventHandler = procedure (Sender: TObject; Event: TutlInputEvent; var DoneEvent: boolean) of object; + TMouseButtons = set of TMouseButton; + TutlEventManager = class + private type + TInputState = record + Keyboard: record + Modifiers: TShiftState; + KeyState: array[Byte] of Boolean; + end; + Mouse: record + ScreenPos, ClientPos: TPoint; + Buttons: TMouseButtons; + end; + Window: record + Active: boolean; + ScreenRect: TRect; + ClientWidth: Integer; + ClientHeight: Integer; + end; + end; + + TEventListener = class + ThreadID: TThreadID; + Synchronous: Boolean; + Filter: TutlEventTypes; + Handler: TutlInputEventHandler; + end; + TEventListenerList = specialize TutlList<TEventListener>; + + TInputEventMsg = class(TutlCallbackMsg) + private + fSender: TObject; + fHandler: TutlInputEventHandler; + fInputEvent: TutlInputEvent; + public + procedure ExecuteCallback; override; + constructor Create(const aSender: TObject; const aHandler: TutlInputEventHandler; const aInputEvent: TutlInputEvent); + destructor Destroy; override; + end; + + TSyncInputEventMsg = class(TutlSyncCallbackMsg) + private + fSender: TObject; + fHandler: TutlInputEventHandler; + fInputEvent: TutlInputEvent; + fDoneEvent: Boolean; + public + property DoneEvent: Boolean read fDoneEvent; + procedure ExecuteCallback; override; + constructor Create(const aSender: TObject; const aHandler: TutlInputEventHandler; const aInputEvent: TutlInputEvent); + destructor Destroy; override; + end; + + private + fEventQueue: TutlInputEventList; + fEventQueueLock: TCriticalSection; + fListeners: TEventListenerList; + protected + fCanonicalState: TInputState; + procedure EventHandlerMouseDown(Sender: TObject; Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer); + procedure EventHandlerMouseUp(Sender: TObject; Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer); + procedure EventHandlerMouseWheel(Sender: TObject; {%H-}Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); + procedure EventHandlerMouseMove(Sender: TObject; {%H-}Shift: TShiftState; X, Y: Integer); + procedure EventHandlerMouseEnter(Sender: TObject); + procedure EventHandlerMouseLeave(Sender: TObject); + + procedure EventHandlerClick(Sender: TObject); + procedure EventHandlerDblClick(Sender: TObject); + + procedure EventHandlerKeyDown(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState); + procedure EventHandlerKeyUp(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState); + + procedure EventHandlerResize(Sender: TObject); + procedure EventHandlerActivate(Sender: TObject); + procedure EventHandlerDeactivate(Sender: TObject); + + function QueuePush(const aEvent: TutlInputEvent): TutlInputEvent; + + function DispatchEvent(const aEvent: TutlInputEvent): boolean; + procedure RecordEvent(const aEvent: TutlInputEvent); + public + property CanonicalState: TInputState read fCanonicalState; + + procedure AttachEvents(const fControl: TCustomForm; aEventMask: TutlEventTypes); + function IsKeyDown(const aChar: Char): Boolean; + + procedure RegisterListener(const aEventMask: TutlEventTypes; const aHandler: TutlInputEventHandler; const aSynchronous: Boolean = false); + procedure UnregisterListener(const aHandler: TutlInputEventHandler); + + procedure DispatchEvents; + + constructor Create; + destructor Destroy; override; + end; + +function utlEventManager: TutlEventManager; + +const + utlInput_Events_Mouse = [MOUSE_DOWN, MOUSE_UP, MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN, MOUSE_MOVE, + MOUSE_ENTER, MOUSE_LEAVE, MOUSE_CLICK, MOUSE_DBL_CLICK]; + utlInput_Events_Keyboard = [KEY_DOWN, KEY_REPEAT, KEY_UP]; + utlInput_Events_Window = [WINDOW_RESIZE, WINDOW_ACTIVATE, WINDOW_DEACTIVATE]; + utlInput_Events_All = utlInput_Events_Mouse+utlInput_Events_Keyboard+utlInput_Events_Window; + +implementation + +uses uutlKeyCodes, uutlLogger, LCLIntf; + +type + TCustomFormVisibilityClass = class(TCustomForm) + published + property OnMouseDown; + property OnMouseMove; + property OnMouseUp; + property OnMouseWheel; + property OnMouseEnter; + property OnMouseLeave; + property OnActivate; + property OnDeactivate; + property OnClick; + property OnDblClick; + end; + +var + utlEventManager_Singleton: TutlEventManager; + +function utlEventManager: TutlEventManager; +begin + if not Assigned(utlEventManager_Singleton) then + utlEventManager_Singleton := TutlEventManager.Create; + result := utlEventManager_Singleton; +end; + +{ TSyncInputEventMsg } + +procedure TutlEventManager.TSyncInputEventMsg.ExecuteCallback; +begin + fHandler(fSender, fInputEvent, fDoneEvent); +end; + +constructor TutlEventManager.TSyncInputEventMsg.Create(const aSender: TObject; + const aHandler: TutlInputEventHandler; const aInputEvent: TutlInputEvent); +begin + inherited Create; + fSender := aSender; + fInputEvent := aInputEvent.Clone; + fHandler := aHandler; + fDoneEvent := false; +end; + +destructor TutlEventManager.TSyncInputEventMsg.Destroy; +begin + FreeAndNil(fInputEvent); + inherited Destroy; +end; + +{ TInputEventMsg } + +procedure TutlEventManager.TInputEventMsg.ExecuteCallback; +var + done: Boolean; +begin + done := false; + fHandler(fSender, fInputEvent, done); +end; + +constructor TutlEventManager.TInputEventMsg.Create(const aSender: TObject; + const aHandler: TutlInputEventHandler; const aInputEvent: TutlInputEvent); +begin + inherited Create; + fSender := aSender; + fInputEvent := aInputEvent.Clone; + fHandler := aHandler; +end; + +destructor TutlEventManager.TInputEventMsg.Destroy; +begin + FreeAndNil(fInputEvent); + inherited Destroy; +end; + +{ TutlInputEvent } + +function TutlInputEvent.CreateInstance: TutlInputEvent; +begin + result := TutlInputEvent.Create(EventType); +end; + +procedure TutlInputEvent.Assign(const aEvent: TutlInputEvent); +begin + EventType := aEvent.EventType; + Timestamp := aEvent.Timestamp; +end; + +function TutlInputEvent.Clone: TutlInputEvent; +begin + result := CreateInstance; + result.Assign(self); +end; + +constructor TutlInputEvent.Create(aType: TutlEventType); +begin + inherited Create; + Timestamp:= GetMicroTime; + EventType:= aType; +end; + +{ TutlMouseEvent } + +function TutlMouseEvent.CreateInstance: TutlInputEvent; +begin + result := TutlMouseEvent.Create(EventType, ClientPos, ScreenPos); +end; + +procedure TutlMouseEvent.Assign(const aEvent: TutlInputEvent); +var + e: TutlMouseEvent; +begin + inherited Assign(aEvent); + e := aEvent as TutlMouseEvent; + Button := e.Button; + ClientPos := e.ClientPos; + ScreenPos := e.ScreenPos; +end; + +constructor TutlMouseEvent.Create(aType: TutlEventType; aButton: TMouseButton; aClientPos, aScreenPos: TPoint); +begin + inherited Create(aType); + Button:= aButton; + ClientPos:= aClientPos; + ScreenPos:= aScreenPos; +end; + +constructor TutlMouseEvent.Create(aType: TutlEventType; aClientPos, aScreenPos: TPoint); +begin + inherited Create(aType); + ClientPos:= aClientPos; + ScreenPos:= aScreenPos; +end; + +{ TutlKeyEvent } + +function TutlKeyEvent.CreateInstance: TutlInputEvent; +begin + result := TutlKeyEvent.Create(EventType, CharCode, KeyCode); +end; + +procedure TutlKeyEvent.Assign(const aEvent: TutlInputEvent); +var + e: TutlKeyEvent; +begin + inherited Assign(aEvent); + e := (aEvent as TutlKeyEvent); + CharCode := e.CharCode; + KeyCode := e.KeyCode; +end; + +constructor TutlKeyEvent.Create(aType: TutlEventType; aCharCode: WideChar; aKeyCode: Word); +begin + inherited Create(aType); + CharCode:= aCharCode; + KeyCode:= aKeyCode; +end; + +{ TutlWindowEvent } + +function TutlWindowEvent.CreateInstance: TutlInputEvent; +begin + result := TutlWindowEvent.Create(EventType, ScreenRect, ClientWidth, ClientHeight); +end; + +procedure TutlWindowEvent.Assign(const aEvent: TutlInputEvent); +var + e: TutlWindowEvent; +begin + inherited Assign(aEvent); + e := (aEvent as TutlWindowEvent); + ScreenRect := e.ScreenRect; + ClientWidth := e.ClientWidth; + ClientHeight := e.ClientHeight; +end; + +constructor TutlWindowEvent.Create(aType: TutlEventType; aScreenRect: TRect; aClientWidth, + aClientHeight: Cardinal); +begin + inherited Create(aType); + ScreenRect:= aScreenRect; + ClientWidth:= aClientWidth; + ClientHeight:= aClientHeight; +end; + +constructor TutlWindowEvent.Create(aType: TutlEventType; aScreenTopLeft: TPoint; aClientWidth, aClientHeight: Cardinal); +begin + inherited Create(aType); + ClientWidth:= aClientWidth; + ClientHeight:= aClientHeight; + + ScreenRect.TopLeft:= aScreenTopLeft; + ScreenRect.BottomRight:= aScreenTopLeft; + inc(ScreenRect.Right, ClientWidth); + inc(ScreenRect.Bottom, ClientHeight); +end; + +{ TutlEventManager } + +{$REGION EventHandler} +procedure TutlEventManager.EventHandlerMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_DOWN, Button, Point(X,Y), TWinControl(Sender).ClientToScreen(Point(X,Y)))); +end; + +procedure TutlEventManager.EventHandlerMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_MOVE, Point(X,Y), TWinControl(Sender).ClientToScreen(Point(X,Y)))); +end; + +procedure TutlEventManager.EventHandlerMouseEnter(Sender: TObject); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_ENTER, TWinControl(Sender).ScreenToClient(Mouse.CursorPos), Mouse.CursorPos)); +end; + +procedure TutlEventManager.EventHandlerMouseLeave(Sender: TObject); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_LEAVE, TWinControl(Sender).ScreenToClient(Mouse.CursorPos), Mouse.CursorPos)); +end; + +procedure TutlEventManager.EventHandlerClick(Sender: TObject); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_CLICK, TWinControl(Sender).ScreenToClient(Mouse.CursorPos), Mouse.CursorPos)); +end; + +procedure TutlEventManager.EventHandlerDblClick(Sender: TObject); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_DBL_CLICK, TWinControl(Sender).ScreenToClient(Mouse.CursorPos), Mouse.CursorPos)); +end; + +procedure TutlEventManager.EventHandlerMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +begin + QueuePush(TutlMouseEvent.Create(MOUSE_UP, Button, Point(X,Y), TWinControl(Sender).ClientToScreen(Point(X,Y)))); +end; + +procedure TutlEventManager.EventHandlerMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); +begin + if WheelDelta < 0 then + QueuePush(TutlMouseEvent.Create(MOUSE_WHEEL_DOWN, MousePos, TWinControl(Sender).ClientToScreen(MousePos))) + else + QueuePush(TutlMouseEvent.Create(MOUSE_WHEEL_UP, MousePos, TWinControl(Sender).ClientToScreen(MousePos))); + Handled:= false; +end; + +procedure TutlEventManager.EventHandlerKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); +var + ch: WideChar; +begin + ch:= VKCodeToCharCode(Key, fCanonicalState.Keyboard.Modifiers); + + if fCanonicalState.Keyboard.KeyState[Key and $FF] then + QueuePush(TutlKeyEvent.Create(KEY_REPEAT, ch, Key)) + else + QueuePush(TutlKeyEvent.Create(KEY_DOWN, ch, Key)); +end; + +procedure TutlEventManager.EventHandlerKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState); +var + ch: WideChar; +begin + ch:= VKCodeToCharCode(Key, fCanonicalState.Keyboard.Modifiers); + QueuePush(TutlKeyEvent.Create(KEY_UP, ch, Key)); +end; + +procedure TutlEventManager.EventHandlerResize(Sender: TObject); +var + w: TControl; +begin + w := (Sender as TControl); + QueuePush(TutlWindowEvent.Create(WINDOW_RESIZE, w.ClientToScreen(Point(0,0)), w.ClientWidth, w.ClientHeight)); +end; + +procedure TutlEventManager.EventHandlerActivate(Sender: TObject); +var + w: TControl; +begin + w := (Sender as TControl); + QueuePush(TutlWindowEvent.Create(WINDOW_ACTIVATE, w.ClientToScreen(Point(0,0)), w.ClientWidth, w.ClientHeight)); +end; + +procedure TutlEventManager.EventHandlerDeactivate(Sender: TObject); +var + w: TControl; +begin + w := (Sender as TControl); + QueuePush(TutlWindowEvent.Create(WINDOW_DEACTIVATE, w.ClientToScreen(Point(0,0)), w.ClientWidth, w.ClientHeight)); +end; +{$ENDREGION} + +function TutlEventManager.QueuePush(const aEvent: TutlInputEvent): TutlInputEvent; +begin + fEventQueueLock.Acquire; + try + if Assigned(fEventQueue) then + fEventQueue.Add(aEvent); + Result:= aEvent; + finally + fEventQueueLock.Release; + end; +end; + +function TutlEventManager.DispatchEvent(const aEvent: TutlInputEvent): boolean; +var + i: integer; + ls: TEventListener; + msg: TSyncInputEventMsg; +begin + Result:= false; + for i:= 0 to fListeners.Count-1 do begin + if aEvent.EventType in fListeners[i].Filter then begin + ls := fListeners[i]; + if (GetCurrentThreadId <> ls.ThreadID) then begin + if (ls.Synchronous) then begin + msg := TSyncInputEventMsg.Create(self, ls.Handler, aEvent); + if utlSendMessage(ls.ThreadID, msg, 5000) = wrSignaled then begin + result := msg.DoneEvent; + msg.Free; //only free on wrSignal, otherwise thread will free message + end + end else + utlPostMessage(ls.ThreadID, TInputEventMsg.Create(self, ls.Handler, aEvent)); + end else + fListeners[i].Handler(Self, aEvent, Result); + end; + if Result then + break; + end; +end; + +procedure TutlEventManager.RecordEvent(const aEvent: TutlInputEvent); + + function GetPressedButtons: TMouseButtons; + begin + result := []; + if (GetKeyState(VK_LBUTTON) < 0) then + result := result + [mbLeft]; + if (GetKeyState(VK_RBUTTON) < 0) then + result := result + [mbRight]; + if (GetKeyState(VK_MBUTTON) < 0) then + result := result + [mbMiddle]; + if (GetKeyState(VK_XBUTTON1) < 0) then + result := result + [mbExtra1]; + if (GetKeyState(VK_XBUTTON2) < 0) then + result := result + [mbExtra2]; + end; + +begin + if aEvent is TutlMouseEvent then + with TutlMouseEvent(aEvent) do begin + fCanonicalState.Mouse.ClientPos := ClientPos; + fCanonicalState.Mouse.ScreenPos := ScreenPos; + case EventType of + MOUSE_DOWN: + Include(fCanonicalState.Mouse.Buttons, Button); + MOUSE_UP: + Exclude(fCanonicalState.Mouse.Buttons, Button); + MOUSE_LEAVE: + fCanonicalState.Mouse.Buttons := []; + MOUSE_ENTER: + fCanonicalState.Mouse.Buttons := GetPressedButtons; + MOUSE_CLICK, + MOUSE_DBL_CLICK, + MOUSE_MOVE, + MOUSE_WHEEL_DOWN, + MOUSE_WHEEL_UP: ; //nothing to record here + end; + end + else if aEvent is TutlKeyEvent then + with TutlKeyEvent(aEvent) do begin + case EventType of + KEY_DOWN, + KEY_REPEAT: begin + fCanonicalState.Keyboard.KeyState[KeyCode and $FF]:= true; + case KeyCode of + VK_SHIFT: include(fCanonicalState.Keyboard.Modifiers, ssShift); + VK_MENU: include(fCanonicalState.Keyboard.Modifiers, ssAlt); + VK_CONTROL: include(fCanonicalState.Keyboard.Modifiers, ssCtrl); + end; + end; + KEY_UP: begin + fCanonicalState.Keyboard.KeyState[KeyCode and $FF]:= false; + case KeyCode of + VK_SHIFT: Exclude(fCanonicalState.Keyboard.Modifiers, ssShift); + VK_MENU: Exclude(fCanonicalState.Keyboard.Modifiers, ssAlt); + VK_CONTROL: Exclude(fCanonicalState.Keyboard.Modifiers, ssCtrl); + end; + end; + end; + if [ssCtrl, ssAlt] - fCanonicalState.Keyboard.Modifiers = [] then + include(fCanonicalState.Keyboard.Modifiers, ssAltGr) + else + exclude(fCanonicalState.Keyboard.Modifiers, ssAltGr); + end + else if aEvent is TutlWindowEvent then + with TutlWindowEvent(aEvent) do begin + case EventType of + WINDOW_ACTIVATE: fCanonicalState.Window.Active:= true; + WINDOW_DEACTIVATE: fCanonicalState.Window.Active:= true; + WINDOW_RESIZE: begin + fCanonicalState.Window.ScreenRect := ScreenRect; + fCanonicalState.Window.ClientWidth := ClientWidth; + fCanonicalState.Window.ClientHeight := ClientHeight; + end; + end; + end +end; + +procedure TutlEventManager.DispatchEvents; +var + i: integer; +begin + fEventQueueLock.Acquire; + try + if Assigned(fEventQueue) then begin + //process ALL events + for i:= 0 to fEventQueue.Count-1 do begin + DispatchEvent(fEventQueue[i]); + RecordEvent(fEventQueue[i]); + end; + //now that we're done, free them + fEventQueue.Clear; + end; + finally + fEventQueueLock.Release; + end; +end; + +procedure TutlEventManager.AttachEvents(const fControl: TCustomForm; aEventMask: TutlEventTypes); +var + ctl: TCustomFormVisibilityClass; +begin + ctl:= TCustomFormVisibilityClass(fControl); + ctl.KeyPreview:= true; + if MOUSE_DOWN in aEventMask then ctl.OnMouseDown:= @EventHandlerMouseDown; + if MOUSE_UP in aEventMask then ctl.OnMouseUp:= @EventHandlerMouseUp; + if (MOUSE_WHEEL_DOWN in aEventMask) or + (MOUSE_WHEEL_UP in aEventMask) then ctl.OnMouseWheel:= @EventHandlerMouseWheel; + if MOUSE_MOVE in aEventMask then ctl.OnMouseMove:= @EventHandlerMouseMove; + if MOUSE_ENTER in aEventMask then ctl.OnMouseEnter := @EventHandlerMouseEnter; + if MOUSE_LEAVE in aEventMask then ctl.OnMouseLeave := @EventHandlerMouseLeave; + if MOUSE_CLICK in aEventMask then ctl.OnClick := @EventHandlerClick; + if MOUSE_DBL_CLICK in aEventMask then ctl.OnDblClick := @EventHandlerDblClick; + + if KEY_DOWN in aEventMask then ctl.OnKeyDown:= @EventHandlerKeyDown; + if KEY_UP in aEventMask then ctl.OnKeyUp:= @EventHandlerKeyUp; + + if WINDOW_RESIZE in aEventMask then ctl.OnResize:= @EventHandlerResize; + if WINDOW_ACTIVATE in aEventMask then ctl.OnActivate:= @EventHandlerActivate; + if WINDOW_DEACTIVATE in aEventMask then ctl.OnDeactivate:= @EventHandlerDeactivate; +end; + +function TutlEventManager.IsKeyDown(const aChar: Char): Boolean; +begin + result := CanonicalState.Keyboard.KeyState[Ord(UpCase(aChar))]; +end; + +procedure TutlEventManager.RegisterListener(const aEventMask: TutlEventTypes; + const aHandler: TutlInputEventHandler; const aSynchronous: Boolean); +var + ls: TEventListener; +begin + UnregisterListener(aHandler); + ls:= TEventListener.Create; + try + ls.Filter := aEventMask; + ls.Handler := aHandler; + ls.ThreadID := GetCurrentThreadId; + ls.Synchronous := aSynchronous; + fListeners.Add(ls); + except + ls.Free; + end; +end; + +procedure TutlEventManager.UnregisterListener(const aHandler: TutlInputEventHandler); +var + i: integer; + m1, m2: TMethod; +begin + m1 := TMethod(aHandler); + for i:= fListeners.Count-1 downto 0 do begin + m2 := TMethod(fListeners[i].Handler); + if (m1.Data = m2.Data) and + (m2.Code = m2.Code)then + fListeners.Delete(i); + end; +end; + +constructor TutlEventManager.Create; +begin + inherited Create; + fEventQueue:= TutlInputEventList.Create(true); + fEventQueueLock:= TCriticalSection.Create; + fListeners:= TEventListenerList.Create(true); +end; + +destructor TutlEventManager.Destroy; +begin + FreeAndNil(fListeners); + fEventQueueLock.Acquire; + try + fEventQueue.Clear; + FreeAndNil(fEventQueue); + finally + fEventQueueLock.Release; + end; + FreeAndNil(fEventQueueLock); + inherited Destroy; +end; + +finalization + if Assigned(utlEventManager_Singleton) then + FreeAndNil(utlEventManager_Singleton); + +end. + diff --git a/uutlExceptions.pas b/uutlExceptions.pas new file mode 100644 index 0000000..6f92263 --- /dev/null +++ b/uutlExceptions.pas @@ -0,0 +1,103 @@ +unit uutlExceptions; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält Definitionen für verschiedene Exceptions } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, syncobjs; + +type + EOutOfRange = class(Exception) + constructor Create(const aIndex, aMin, aMax: Integer); + end; + + EUnknownType = class(Exception) + public + constructor Create(const aObj: TObject); + end; + + EArgumentNil = class(Exception) + public + constructor Create(const aArgName: String); + end; + + EArgument = class(Exception) + public + constructor Create(const aArg, aMsg: String); + constructor Create(const aMsg: String); + end; + EParameter = EArgument; + + EFileDoesntExists = class(Exception) + public + constructor Create(const aFilename: string); + end; + EFileNotFound = EFileDoesntExists; + + EInvalidFile = class(Exception); + + EInvalidOperation = class(Exception); + + ENotSupported = class(Exception); + + EWait = class(Exception) + private + fWaitResult: TWaitResult; + public + property WaitResult: TWaitResult read fWaitResult; + constructor Create(const msg: string; const aWaitResult: TWaitResult); + end; + +implementation + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EOutOfRange.Create(const aIndex, aMin, aMax: Integer); +begin + inherited Create(format('index (%d) out of range (%d:%d)', [aIndex, aMin, aMax])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EUnknownType.Create(const aObj: TObject); +begin + inherited Create(format('unknown type: %s', [aObj.ClassName])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EArgumentNil.Create(const aArgName: String); +begin + inherited Create(format('argument ''%s'' can not be nil!', [aArgName])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EArgument.Create(const aArg, aMsg: String); +begin + inherited Create(format('invalid argument "%s" - %s', [aArg, aMsg])) +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EArgument.Create(const aMsg: String); +begin + inherited Create(aMsg); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EFileDoesntExists.Create(const aFilename: string); +begin + inherited Create('file doesn''t exists: ' + aFilename); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor EWait.Create(const msg: string; const aWaitResult: TWaitResult); +begin + inherited Create(msg); + fWaitResult := aWaitResult; +end; + + +end. + diff --git a/uutlGenerics.pas b/uutlGenerics.pas new file mode 100644 index 0000000..77d6a9e --- /dev/null +++ b/uutlGenerics.pas @@ -0,0 +1,1413 @@ +unit uutlGenerics; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit implementiert allgemein nützliche ausschließlich-generische Klassen } + +{$mode objfpc}{$H+} +{$modeswitch nestedprocvars} + +interface + +uses + Classes, SysUtils, typinfo; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic IutlEqualityComparer<T> = interface + function EqualityCompare(const i1, i2: T): Boolean; + end; + + generic TutlEqualityComparer<T> = class(TInterfacedObject, specialize IutlEqualityComparer<T>) + public + function EqualityCompare(const i1, i2: T): Boolean; + end; + + generic TutlEventEqualityComparer<T> = class(TInterfacedObject, specialize IutlEqualityComparer<T>) + public type + TEqualityEvent = function(const i1, i2: T): Boolean; + TEqualityEventO = function(const i1, i2: T): Boolean of object; + TEqualityEventN = function(const i1, i2: T): Boolean is nested; + private type + TEqualityEventType = (eetNormal, eetObject, eetNested); + private + fEvent: TEqualityEvent; + fEventO: TEqualityEventO; + fEventN: TEqualityEventN; + fEventType: TEqualityEventType; + public + function EqualityCompare(const i1, i2: T): Boolean; + constructor Create(const aEvent: TEqualityEvent); overload; + constructor Create(const aEvent: TEqualityEventO); overload; + constructor Create(const aEvent: TEqualityEventN); overload; + { HINT: you need to activate "$modeswitch nestedprocvars" when you want to use nested callbacks } + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic IutlComparer<T> = interface + function Compare(const i1, i2: T): Integer; + end; + + generic TutlComparer<T> = class(TInterfacedObject, specialize IutlComparer<T>) + public + function Compare(const i1, i2: T): Integer; + end; + + generic TutlEventComparer<T> = class(TInterfacedObject, specialize IutlComparer<T>) + public type + TEvent = function(const i1, i2: T): Integer; + TEventO = function(const i1, i2: T): Integer of object; + TEventN = function(const i1, i2: T): Integer is nested; + private type + TEventType = (etNormal, etObject, etNested); + private + fEvent: TEvent; + fEventO: TEventO; + fEventN: TEventN; + fEventType: TEventType; + public + function Compare(const i1, i2: T): Integer; + constructor Create(const aEvent: TEvent); overload; + constructor Create(const aEvent: TEventO); overload; + constructor Create(const aEvent: TEventN); overload; + { HINT: you need to activate "$modeswitch nestedprocvars" when you want to use nested callbacks } + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlListBase<T> = class(TObject) + private type + TListItem = packed record + data: T; + end; + PListItem = ^TListItem; + + public type + TEnumerator = class(TObject) + private + fList: TFPList; + fPosition: Integer; + function GetCurrent: T; + public + property Current: T read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aList: TFPList); + end; + + private + fList: TFPList; + fOwnsObjects: Boolean; + + protected + property List: TFPList read fList; + + function GetCount: Integer; + function GetItem(const aIndex: Integer): T; + procedure SetCount(const aValue: Integer); + procedure SetItem(const aIndex: Integer; const aItem: T); + + function CreateItem: PListItem; virtual; + procedure DestroyItem(const aItem: PListItem; const aFreeItem: Boolean = true); virtual; + procedure InsertIntern(const aIndex: Integer; const aItem: T); virtual; + procedure DeleteIntern(const aIndex: Integer; const aFreeItem: Boolean = true); + + public + property OwnsObjects: Boolean read fOwnsObjects write fOwnsObjects; + + function GetEnumerator: TEnumerator; + procedure Clear; + + constructor Create(const aOwnsObjects: Boolean = true); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + { a simple list without the ability to compare objects (e.g. for IndexOf, Remove, Extract) } + generic TutlSimpleList<T> = class(specialize TutlListBase<T>) + public type + IComparer = specialize IutlComparer<T>; + TSortDirection = (sdAscending, sdDescending); + private + function Split(aComparer: IComparer; const aDirection: TSortDirection; const aLeft, aRight: Integer): Integer; + procedure QuickSort(aComparer: IComparer; const aDirection: TSortDirection; const aLeft, aRight: Integer); + public + property Items[const aIndex: Integer]: T read GetItem write SetItem; default; + property Count: Integer read GetCount write SetCount; + + function Add(const aItem: T): Integer; + procedure Insert(const aIndex: Integer; const aItem: T); + + procedure Exchange(const aIndex1, aIndex2: Integer); + procedure Move(const aCurIndex, aNewIndex: Integer); + procedure Sort(aComparer: IComparer; const aDirection: TSortDirection = sdAscending); + + procedure Delete(const aIndex: Integer); + + function First: T; + procedure PushFirst(const aItem: T); + function PopFirst(const aFreeItem: Boolean = false): T; + + function Last: T; + procedure PushLast(const aItem: T); + function PopLast(const aFreeItem: Boolean = false): T; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlCustomList<T> = class(specialize TutlSimpleList<T>) + public type + IEqualityComparer = specialize IutlEqualityComparer<T>; + private + fEqualityComparer: IEqualityComparer; + public + function IndexOf(const aItem: T): Integer; + function Extract(const aItem: T; const aDefault: T): T; + function Remove(const aItem: T): Integer; + + constructor Create(aEqualityComparer: IEqualityComparer; const aOwnsObjects: Boolean = true); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlList<T> = class(specialize TutlCustomList<T>) + public type + TEqualityComparer = specialize TutlEqualityComparer<T>; + public + constructor Create(const aOwnsObjects: Boolean = true); + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlCustomHashSet<T> = class(specialize TutlListBase<T>) + public type + IComparer = specialize IutlComparer<T>; + private + fComparer: IComparer; + function SearchItem(const aMin, aMax: Integer; const aItem: T; out aIndex: Integer): Integer; + public + property Items[const aIndex: Integer]: T read GetItem; default; + property Count: Integer read GetCount; + + function Add(const aItem: T): Boolean; + function Contains(const aItem: T): Boolean; + function IndexOf(const aItem: T): Integer; + function Remove(const aItem: T): Boolean; + procedure Delete(const aIndex: Integer); + + constructor Create(aComparer: IComparer; const aOwnsObjects: Boolean = true); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlHashSet<T> = class(specialize TutlCustomHashSet<T>) + public type + TComparer = specialize TutlComparer<T>; + public + constructor Create(const aOwnsObjects: Boolean = true); + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EutlMap = class(Exception); + generic TutlCustomMap<TKey, TValue> = class(TObject) + public type + IComparer = specialize IutlComparer<TKey>; + TKeyValuePair = packed record + Key: TKey; + Value: TValue; + end; + private type + THashSetBase = specialize TutlCustomHashSet<TKeyValuePair>; + THashSet = class(THashSetBase) + protected + procedure DestroyItem(const aItem: PListItem; const aFreeItem: Boolean = true); override; + public + property Items[const aIndex: Integer]: TKeyValuePair read GetItem write SetItem; default; + end; + + TKVPComparer = class(TInterfacedObject, THashSet.IComparer) + private + fComparer: IComparer; + public + function Compare(const i1, i2: TKeyValuePair): Integer; + constructor Create(aComparer: IComparer); + destructor Destroy; override; + end; + + TValueEnumerator = class(TObject) + private + fHashSet: THashSet; + fPos: Integer; + function GetCurrent: TValue; + public + property Current: TValue read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aHashSet: THashSet); + end; + + TKeyEnumerator = class(TObject) + private + fHashSet: THashSet; + fPos: Integer; + function GetCurrent: TKey; + public + property Current: TKey read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aHashSet: THashSet); + end; + + TKeyValuePairEnumerator = class(TObject) + private + fHashSet: THashSet; + fPos: Integer; + function GetCurrent: TKeyValuePair; + public + property Current: TKeyValuePair read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aHashSet: THashSet); + end; + + TKeyWrapper = class(TObject) + private + fHashSet: THashSet; + function GetItem(const aIndex: Integer): TKey; + function GetCount: Integer; + public + property Items[const aIndex: Integer]: TKey read GetItem; default; + property Count: Integer read GetCount; + function GetEnumerator: TKeyEnumerator; + constructor Create(const aHashSet: THashSet); + end; + + TKeyValuePairWrapper = class(TObject) + private + fHashSet: THashSet; + function GetItem(const aIndex: Integer): TKeyValuePair; + function GetCount: Integer; + public + property Items[const aIndex: Integer]: TKeyValuePair read GetItem; default; + property Count: Integer read GetCount; + function GetEnumerator: TKeyValuePairEnumerator; + constructor Create(const aHashSet: THashSet); + end; + + private + fComparer: IComparer; + fHashSet: THashSet; + fKeyWrapper: TKeyWrapper; + fKeyValuePairWrapper: TKeyValuePairWrapper; + + function GetValues(const aKey: TKey): TValue; + function GetValueAt(const aIndex: Integer): TValue; + function GetCount: Integer; + + procedure SetValueAt(const aIndex: Integer; aValue: TValue); + procedure SetValues(const aKey: TKey; aValue: TValue); + public + property Values [const aKey: TKey]: TValue read GetValues write SetValues; default; + property ValueAt[const aIndex: Integer]: TValue read GetValueAt write SetValueAt; + property Keys: TKeyWrapper read fKeyWrapper; + property KeyValuePairs: TKeyValuePairWrapper read fKeyValuePairWrapper; + property Count: Integer read GetCount; + + procedure Add(const aKey: TKey; const aValue: TValue); + function IndexOf(const aKey: TKey): Integer; + function Contains(const aKey: TKey): Boolean; + procedure Delete(const aKey: TKey); + procedure DeleteAt(const aIndex: Integer); + procedure Clear; + + function GetEnumerator: TValueEnumerator; + + constructor Create(aComparer: IComparer; const aOwnsObjects: Boolean = true); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlMap<TKey, TValue> = class(specialize TutlCustomMap<TKey, TValue>) + public type + TComparer = specialize TutlComparer<TKey>; + public + constructor Create(const aOwnsObjects: Boolean = true); + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlInterfaceList<T> = class(TInterfaceList) + private type + TInterfaceEnumerator = class(TObject) + private + fList: TInterfaceList; + fPos: Integer; + function GetCurrent: T; + public + property Current: T read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aList: TInterfaceList); + end; + + private + function Get(i : Integer): T; + procedure Put(i : Integer; aItem : T); + public + property Items[Index : Integer]: T read Get write Put; default; + + function First: T; + function IndexOf(aItem : T): Integer; + function Add(aItem : IUnknown): Integer; + procedure Insert(i : Integer; aItem : T); + function Last : T; + function Remove(aItem : T): Integer; + + function GetEnumerator: TInterfaceEnumerator; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlEnumHelper<T> = class(TObject) + private type + TValueArray = array of T; + public + class function ToString(aValue: T): String; reintroduce; + class function TryToEnum(aStr: String; out aValue: T): Boolean; + class function ToEnum(aStr: String): T; overload; + class function ToEnum(aStr: String; const aDefault: T): T; overload; + class function Values: TValueArray; + end; + + function utlFreeOrFinalize(var obj; const aTypeInfo: PTypeInfo; const aFreeObj: Boolean = true): Boolean; + +implementation + +uses + uutlExceptions; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Helper//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +operator < (const i1, i2: TObject): Boolean; inline; +begin + result := PtrUInt(i1) < PtrUInt(i2); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +operator > (const i1, i2: TObject): Boolean; inline; +begin + result := PtrUInt(i1) > PtrUInt(i2); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlFreeOrFinalize(var obj; const aTypeInfo: PTypeInfo; const aFreeObj: Boolean = true): Boolean; +var + o: TObject; +begin + result := true; + case aTypeInfo^.Kind of + tkClass: begin + if (aFreeObj) then begin + o := TObject(obj); + Pointer(obj) := nil; + o.Free; + end; + end; + + tkInterface: begin + IUnknown(obj) := nil; + end; + + tkAString: begin + AnsiString(Obj) := ''; + end; + + tkUString: begin + UnicodeString(Obj) := ''; + end; + + tkString: begin + String(Obj) := ''; + end; + else + result := false; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlEqualityComparer////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlEqualityComparer.EqualityCompare(const i1, i2: T): Boolean; +begin + result := (i1 = i2); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlEventEqualityComparer.EqualityCompare(const i1, i2: T): Boolean; +begin + case fEventType of + eetNormal: result := fEvent(i1, i2); + eetObject: result := fEventO(i1, i2); + eetNested: result := fEventN(i1, i2); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventEqualityComparer.Create(const aEvent: TEqualityEvent); +begin + inherited Create; + fEvent := aEvent; + fEventType := eetNormal; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventEqualityComparer.Create(const aEvent: TEqualityEventO); +begin + inherited Create; + fEventO := aEvent; + fEventType := eetObject; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventEqualityComparer.Create(const aEvent: TEqualityEventN); +begin + inherited Create; + fEventN := aEvent; + fEventType := eetNested; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlComparer////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlComparer.Compare(const i1, i2: T): Integer; +begin + if (i1 < i2) then + result := -1 + else if (i1 > i2) then + result := 1 + else + result := 0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlEventComparer.Compare(const i1, i2: T): Integer; +begin + case fEventType of + etNormal: result := fEvent(i1, i2); + etObject: result := fEventO(i1, i2); + etNested: result := fEventN(i1, i2); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventComparer.Create(const aEvent: TEvent); +begin + inherited Create; + fEvent := aEvent; + fEventType := etNormal; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventComparer.Create(const aEvent: TEventO); +begin + inherited Create; + fEventO := aEvent; + fEventType := etObject; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlEventComparer.Create(const aEvent: TEventN); +begin + inherited Create; + fEventN := aEvent; + fEventType := etNested; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlListBase////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlListBase.TEnumerator.GetCurrent: T; +begin + result := PListItem(fList[fPosition])^.data; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlListBase.TEnumerator.MoveNext: Boolean; +begin + inc(fPosition); + result := (fPosition < fList.Count) +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlListBase.TEnumerator.Create(const aList: TFPList); +begin + inherited Create; + fList := aList; + fPosition := -1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlListBase////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlListBase.GetCount: Integer; +begin + result := fList.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlListBase.GetItem(const aIndex: Integer): T; +begin + if (aIndex >= 0) and (aIndex < fList.Count) then + result := PListItem(fList[aIndex])^.data + else + raise EOutOfRange.Create(aIndex, 0, fList.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListBase.SetCount(const aValue: Integer); +var + item: PListItem; +begin + if (aValue < 0) then + raise EArgument.Create('new value for count must be positiv'); + while (aValue > fList.Count) do begin + item := CreateItem; + FillByte(item^, SizeOf(item^), 0); + fList.Add(item); + end; + while (aValue < fList.Count) do + DeleteIntern(fList.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListBase.SetItem(const aIndex: Integer; const aItem: T); +var + item: PListItem; +begin + if (aIndex >= 0) and (aIndex < fList.Count) then begin + item := PListItem(fList[aIndex]); + utlFreeOrFinalize(item^, TypeInfo(item^), fOwnsObjects); + item^.data := aItem; + end else + raise EOutOfRange.Create(aIndex, 0, fList.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlListBase.CreateItem: PListItem; +begin + new(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListBase.DestroyItem(const aItem: PListItem; const aFreeItem: Boolean); +begin + utlFreeOrFinalize(aItem^.data, TypeInfo(aItem^.data), fOwnsObjects and aFreeItem); + Dispose(aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListBase.InsertIntern(const aIndex: Integer; const aItem: T); +var + item: PListItem; +begin + item := CreateItem; + try + item^.data := aItem; + fList.Insert(aIndex, item); + except + DestroyItem(item, false); + raise; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListBase.DeleteIntern(const aIndex: Integer; const aFreeItem: Boolean); +var + item: PListItem; +begin + if (aIndex >= 0) and (aIndex < fList.Count) then begin + item := PListItem(fList[aIndex]); + fList.Delete(aIndex); + DestroyItem(item, aFreeItem); + end else + raise EOutOfRange.Create(aIndex, 0, fList.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlListBase.GetEnumerator: TEnumerator; +begin + result := TEnumerator.Create(fList); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlListBase.Clear; +begin + while (fList.Count > 0) do + DeleteIntern(fList.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlListBase.Create(const aOwnsObjects: Boolean); +begin + inherited Create; + fOwnsObjects := aOwnsObjects; + fList := TFPList.Create; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlListBase.Destroy; +begin + Clear; + FreeAndNil(fList); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlSimpleList//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSimpleList.Split(aComparer: IComparer; const aDirection: TSortDirection; const aLeft, aRight: Integer): Integer; +var + i, j: Integer; + pivot: T; +begin + i := aLeft; + j := aRight - 1; + pivot := GetItem(aRight); + repeat + while ((aDirection = sdAscending) and (aComparer.Compare(GetItem(i), pivot) <= 0) or + (aDirection = sdDescending) and (aComparer.Compare(GetItem(i), pivot) >= 0)) and + (i < aRight) do inc(i); + + while ((aDirection = sdAscending) and (aComparer.Compare(GetItem(j), pivot) >= 0) or + (aDirection = sdDescending) and (aComparer.Compare(GetItem(j), pivot) <= 0)) and + (j > aLeft) do dec(j); + + if (i < j) then + Exchange(i, j); + until (i >= j); + + if ((aDirection = sdAscending) and (aComparer.Compare(GetItem(i), pivot) > 0)) or + ((aDirection = sdDescending) and (aComparer.Compare(GetItem(i), pivot) < 0)) then + Exchange(i, aRight); + + result := i; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.QuickSort(aComparer: IComparer; const aDirection: TSortDirection; const aLeft, aRight: Integer); +var + s: Integer; +begin + if (aLeft < aRight) then begin + s := Split(aComparer, aDirection, aLeft, aRight); + QuickSort(aComparer, aDirection, aLeft, s - 1); + QuickSort(aComparer, aDirection, s + 1, aRight); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSimpleList.Add(const aItem: T): Integer; +begin + result := Count; + InsertIntern(result, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.Insert(const aIndex: Integer; const aItem: T); +begin + InsertIntern(aIndex, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.Exchange(const aIndex1, aIndex2: Integer); +begin + if (aIndex1 < 0) or (aIndex1 >= Count) then + raise EOutOfRange.Create(aIndex1, 0, Count-1); + if (aIndex2 < 0) or (aIndex2 >= Count) then + raise EOutOfRange.Create(aIndex2, 0, Count-1); + fList.Exchange(aIndex1, aIndex2); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.Move(const aCurIndex, aNewIndex: Integer); +begin + if (aCurIndex < 0) or (aCurIndex >= Count) then + raise EOutOfRange.Create(aCurIndex, 0, Count-1); + if (aNewIndex < 0) or (aNewIndex >= Count) then + raise EOutOfRange.Create(aNewIndex, 0, Count-1); + fList.Move(aCurIndex, aNewIndex); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.Sort(aComparer: IComparer; const aDirection: TSortDirection); +begin + QuickSort(aComparer, aDirection, 0, fList.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.Delete(const aIndex: Integer); +begin + DeleteIntern(aIndex); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSimpleList.First: T; +begin + result := Items[0]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.PushFirst(const aItem: T); +begin + InsertIntern(0, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSimpleList.PopFirst(const aFreeItem: Boolean): T; +begin + if aFreeItem then + FillByte(result{%H-}, SizeOf(result), 0) + else + result := First; + DeleteIntern(0, aFreeItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSimpleList.Last: T; +begin + result := Items[Count-1]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSimpleList.PushLast(const aItem: T); +begin + InsertIntern(Count, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSimpleList.PopLast(const aFreeItem: Boolean): T; +begin + if aFreeItem then + FillByte(result{%H-}, SizeOf(result), 0) + else + result := Last; + DeleteIntern(Count-1, aFreeItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomList//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomList.IndexOf(const aItem: T): Integer; +var + c: Integer; +begin + c := List.Count; + result := 0; + while (result < c) and + not fEqualityComparer.EqualityCompare(PListItem(List[result])^.data, aItem) do + inc(result); + if (result >= c) then + result := -1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomList.Extract(const aItem: T; const aDefault: T): T; +var + i: Integer; +begin + i := IndexOf(aItem); + if (i >= 0) then begin + result := Items[i]; + DeleteIntern(i, false); + end else + result := aDefault; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomList.Remove(const aItem: T): Integer; +begin + result := IndexOf(aItem); + if (result >= 0) then + DeleteIntern(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomList.Create(aEqualityComparer: IEqualityComparer; const aOwnsObjects: Boolean); +begin + inherited Create(aOwnsObjects); + fEqualityComparer := aEqualityComparer; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlCustomList.Destroy; +begin + fEqualityComparer := nil; + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlList////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlList.Create(const aOwnsObjects: Boolean); +begin + inherited Create(TEqualityComparer.Create, aOwnsObjects); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomHashSet///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomHashSet.SearchItem(const aMin, aMax: Integer; const aItem: T; out aIndex: Integer): Integer; +var + i, cmp: Integer; +begin + if (aMin <= aMax) then begin + i := aMin + Trunc((aMax - aMin) / 2); + cmp := fComparer.Compare(aItem, GetItem(i)); + if (cmp = 0) then + result := i + else if (cmp < 0) then + result := SearchItem(aMin, i-1, aItem, aIndex) + else if (cmp > 0) then + result := SearchItem(i+1, aMax, aItem, aIndex); + end else begin + result := -1; + aIndex := aMin; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomHashSet.Add(const aItem: T): Boolean; +var + i: Integer; +begin + result := (SearchItem(0, List.Count-1, aItem, i) < 0); + if result then + InsertIntern(i, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomHashSet.Contains(const aItem: T): Boolean; +var + tmp: Integer; +begin + result := (SearchItem(0, List.Count-1, aItem, tmp) >= 0); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomHashSet.IndexOf(const aItem: T): Integer; +var + tmp: Integer; +begin + result := SearchItem(0, List.Count-1, aItem, tmp); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomHashSet.Remove(const aItem: T): Boolean; +var + i, tmp: Integer; +begin + i := SearchItem(0, List.Count-1, aItem, tmp); + result := (i >= 0); + if result then + DeleteIntern(i); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomHashSet.Delete(const aIndex: Integer); +begin + DeleteIntern(aIndex); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomHashSet.Create(aComparer: IComparer; const aOwnsObjects: Boolean); +begin + inherited Create(aOwnsObjects); + fComparer := aComparer; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlCustomHashSet.Destroy; +begin + fComparer := nil; + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlHashSet/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlHashSet.Create(const aOwnsObjects: Boolean); +begin + inherited Create(TComparer.Create, aOwnsObjects); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.THashSet//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.THashSet.DestroyItem(const aItem: PListItem; const aFreeItem: Boolean); +begin + utlFreeOrFinalize(aItem^.data.key, TypeInfo(aItem^.data.key), aFreeItem and OwnsObjects); + utlFreeOrFinalize(aItem^.data.value, TypeInfo(aItem^.data.value), aFreeItem and OwnsObjects); + inherited DestroyItem(aItem, aFreeItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.TKVPComparer//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKVPComparer.Compare(const i1, i2: TKeyValuePair): Integer; +begin + result := fComparer.Compare(i1.Key, i2.Key); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.TKVPComparer.Create(aComparer: IComparer); +begin + inherited Create; + fComparer := aComparer; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlCustomMap.TKVPComparer.Destroy; +begin + fComparer := nil; + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.TValueEnumerator//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TValueEnumerator.GetCurrent: TValue; +begin + result := fHashSet[fPos].Value; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TValueEnumerator.MoveNext: Boolean; +begin + inc(fPos); + result := (fPos < fHashSet.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.TValueEnumerator.Create(const aHashSet: THashSet); +begin + inherited Create; + fHashSet := aHashSet; + fPos := -1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.TKeyEnumerator////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyEnumerator.GetCurrent: TKey; +begin + result := fHashSet[fPos].Key; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyEnumerator.MoveNext: Boolean; +begin + inc(fPos); + result := (fPos < fHashSet.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.TKeyEnumerator.Create(const aHashSet: THashSet); +begin + inherited Create; + fHashSet := aHashSet; + fPos := -1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.TKeyValuePairEnumerator///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyValuePairEnumerator.GetCurrent: TKeyValuePair; +begin + result := fHashSet[fPos]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyValuePairEnumerator.MoveNext: Boolean; +begin + inc(fPos); + result := (fPos < fHashSet.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.TKeyValuePairEnumerator.Create(const aHashSet: THashSet); +begin + inherited Create; + fHashSet := aHashSet; + fPos := -1; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.TKeyWrapper///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyWrapper.GetItem(const aIndex: Integer): TKey; +begin + result := fHashSet[aIndex].Key; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyWrapper.GetCount: Integer; +begin + result := fHashSet.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyWrapper.GetEnumerator: TKeyEnumerator; +begin + result := TKeyEnumerator.Create(fHashSet); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.TKeyWrapper.Create(const aHashSet: THashSet); +begin + inherited Create; + fHashSet := aHashSet; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap.TKeyValuePairWrapper//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyValuePairWrapper.GetItem(const aIndex: Integer): TKeyValuePair; +begin + result := fHashSet[aIndex]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyValuePairWrapper.GetCount: Integer; +begin + result := fHashSet.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.TKeyValuePairWrapper.GetEnumerator: TKeyValuePairEnumerator; +begin + result := TKeyValuePairEnumerator.Create(fHashSet); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.TKeyValuePairWrapper.Create(const aHashSet: THashSet); +begin + inherited Create; + fHashSet := aHashSet; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCustomMap///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.GetValues(const aKey: TKey): TValue; +var + i: Integer; + kvp: TKeyValuePair; +begin + kvp.Key := aKey; + i := fHashSet.IndexOf(kvp); + if (i < 0) then + FillByte(result{%H-}, SizeOf(result), 0) + else + result := fHashSet[i].Value; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.GetValueAt(const aIndex: Integer): TValue; +begin + result := fHashSet[aIndex].Value; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.GetCount: Integer; +begin + result := fHashSet.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.SetValues(const aKey: TKey; aValue: TValue); +var + i: Integer; + kvp: TKeyValuePair; +begin + kvp.Key := aKey; + kvp.Value := aValue; + i := fHashSet.IndexOf(kvp); + if (i < 0) then + raise EutlMap.Create('key not found'); + fHashSet[i] := kvp; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.SetValueAt(const aIndex: Integer; aValue: TValue); +var + kvp: TKeyValuePair; +begin + kvp := fHashSet[aIndex]; + kvp.Value := aValue; + fHashSet[aIndex] := kvp; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.Add(const aKey: TKey; const aValue: TValue); +var + kvp: TKeyValuePair; +begin + kvp.Key := aKey; + kvp.Value := aValue; + if not fHashSet.Add(kvp) then + raise EutlMap.Create('key is already in list'); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.IndexOf(const aKey: TKey): Integer; +var + kvp: TKeyValuePair; +begin + kvp.Key := aKey; + result := fHashSet.IndexOf(kvp); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.Contains(const aKey: TKey): Boolean; +var + kvp: TKeyValuePair; +begin + kvp.Key := aKey; + result := (fHashSet.IndexOf(kvp) >= 0); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.Delete(const aKey: TKey); +var + kvp: TKeyValuePair; +begin + kvp.Key := aKey; + if not fHashSet.Remove(kvp) then + raise EutlMap.Create('key not found'); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.DeleteAt(const aIndex: Integer); +begin + fHashSet.Delete(aIndex); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCustomMap.Clear; +begin + fHashSet.Clear; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlCustomMap.GetEnumerator: TValueEnumerator; +begin + result := TValueEnumerator.Create(fHashSet); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCustomMap.Create(aComparer: IComparer; const aOwnsObjects: Boolean); +begin + inherited Create; + fComparer := aComparer; + fHashSet := THashSet.Create(TKVPComparer.Create(fComparer), aOwnsObjects); + fKeyWrapper := TKeyWrapper.Create(fHashSet); + fKeyValuePairWrapper := TKeyValuePairWrapper.Create(fHashSet); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlCustomMap.Destroy; +begin + FreeAndNil(fKeyValuePairWrapper); + FreeAndNil(fKeyWrapper); + FreeAndNil(fHashSet); + fComparer := nil; + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMap/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMap.Create(const aOwnsObjects: Boolean); +begin + inherited Create(TComparer.Create, aOwnsObjects); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlInterfaceList.TInterfaceEnumerator//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.TInterfaceEnumerator.GetCurrent: T; +begin + result := T(fList[fPos]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.TInterfaceEnumerator.MoveNext: Boolean; +begin + inc(fPos); + result := (fPos < fList.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlInterfaceList.TInterfaceEnumerator.Create(const aList: TInterfaceList); +begin + inherited Create; + fPos := -1; + fList := aList; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlInterfaceList///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.Get(i : Integer): T; +begin + result := T(inherited Get(i)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlInterfaceList.Put(i : Integer; aItem : T); +begin + inherited Put(i, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.First: T; +begin + result := T(inherited First); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.IndexOf(aItem : T): Integer; +begin + result := inherited IndexOf(aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.Add(aItem : IUnknown): Integer; +begin + result := inherited Add(aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlInterfaceList.Insert(i : Integer; aItem : T); +begin + inherited Insert(i, aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.Last : T; +begin + result := T(inherited Last); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.Remove(aItem : T): Integer; +begin + result := inherited Remove(aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlInterfaceList.GetEnumerator: TInterfaceEnumerator; +begin + result := TInterfaceEnumerator.Create(self); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlEnumHelper//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlEnumHelper.ToString(aValue: T): String; +Var + PS: PShortString; + TI: PTypeInfo; + PT: PTypeData; + num: Integer; +begin + TI := TypeInfo(T); + PT := GetTypeData(TI); + if TI^.Kind = tkBool then begin + case Integer(aValue) of + 0,1: + Result:=BooleanIdents[Boolean(aValue)]; + else + Result:=''; + end; + end else begin + num := Integer(aValue); + if (num >= PT^.MinValue) and (num <= PT^.MaxValue) then begin + PS := @PT^.NameList; + dec(num, PT^.MinValue); + while num > 0 do begin + PS := PShortString(pointer(PS) + PByte(PS)^ + 1); + Dec(Num); + end; + Result := PS^; + end else + Result := ''; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlEnumHelper.TryToEnum(aStr: String; out aValue: T): Boolean; +Var + PS: PShortString; + PT: PTypeData; + Count: longint; + sName: shortstring; + TI: PTypeInfo; +begin + TI := TypeInfo(T); + PT := GetTypeData(TI); + Result := False; + if Length(aStr) = 0 then + exit; + sName := aStr; + + if TI^.Kind = tkBool then begin + If CompareText(BooleanIdents[false], aStr) = 0 then + aValue := T(0) + else if CompareText(BooleanIdents[true], aStr) = 0 then + aValue := T(1); + Result := true; + end else begin + PS := @PT^.NameList; + Count := 0; + While (PByte(PS)^ <> 0) do begin + If ShortCompareText(PS^, sName) = 0 then begin + aValue := T(Count + PT^.MinValue); + exit(true); + end; + PS := PShortString(pointer(PS) + PByte(PS)^ + 1); + Inc(Count); + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlEnumHelper.ToEnum(aStr: String): T; +begin + if not TryToEnum(aStr, result) then + raise EConvertError.CreateFmt('"%s" is an invalid %s',[aStr, PTypeInfo(TypeInfo(T))^.Name]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlEnumHelper.ToEnum(aStr: String; const aDefault: T): T; +begin + if not TryToEnum(aStr, result) then + result := aDefault; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlEnumHelper.Values: TValueArray; +Var + TI: PTypeInfo; + PT: PTypeData; + i,j: integer; +begin + TI := TypeInfo(T); + PT := GetTypeData(TI); + if TI^.Kind = tkBool then begin + SetLength(Result, 2); + Result[0]:= T(true); + Result[1]:= T(false); + end else begin + SetLength(Result, PT^.MaxValue - PT^.MinValue + 1); + j:= 0; + for i:= PT^.MinValue to PT^.MaxValue do begin + Result[j]:= T(i); + inc(j); + end; + end; +end; + +end. + diff --git a/uutlGraph.pas b/uutlGraph.pas new file mode 100644 index 0000000..f1b7495 --- /dev/null +++ b/uutlGraph.pas @@ -0,0 +1,413 @@ +unit uutlGraph; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit implementiert einen generischen Graphen } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, contnrs, uutlCommon; + +type + TutlGraph = class; + TutlGraphNodeData = class(TObject) + public + constructor Create; virtual; + end; + TutlGraphNodeDataClass = class of TutlGraphNodeData; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlGraphNode = class; + TutlGraphNodeClass = class of TutlGraphNode; + TutlGraphNode = class(TutlInterfaceNoRefCount) + private type + TNodeEnumerator = class(TObject) + private + fOwner: TutlGraphNode; + fPos: Integer; + function GetCurrent: TutlGraphNode; + public + property Current: TutlGraphNode read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aOwner: TutlGraphNode); + end; + protected + fParent: TutlGraphNode; + fOwner: TutlGraph; + fData: TutlGraphNodeData; + fItems: TObjectList; + + class function GetDataClass: TutlGraphNodeDataClass; virtual; + + function GetCount: Integer; virtual; + function GetItems(const aIndex: Integer): TutlGraphNode; virtual; + function AttachNode(const aNode: TutlGraphNode): Boolean; virtual; + function DetachNode(const aNode: TutlGraphNode): Boolean; virtual; + public + property Parent: TutlGraphNode read fParent; + property Owner: TutlGraph read fOwner; + property Data: TutlGraphNodeData read fData; + property Count: Integer read GetCount; + property Items[const aIndex: Integer]: TutlGraphNode read GetItems; default; + + function AddItem: TutlGraphNode; + function IndexOf(const aItem: TutlGraphNode): Integer; + procedure DelItem(const aIndex: Integer); + procedure Clear; + function IsParent(const aNode: TutlGraphNode): Boolean; + function Move(const aParent: TutlGraphNode): Boolean; + + function GetEnumerator: TNodeEnumerator; + + constructor Create(const aParent: TutlGraphNode; const aOwner: TutlGraph); + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlGraph = class(TutlInterfaceNoRefCount) + protected + fRootNode: TutlGraphNode; + + class function GetItemClass: TutlGraphNodeClass; virtual; + public + property RootNode: TutlGraphNode read fRootNode; + + constructor Create; + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlGenericGraphNode<GData: TutlGraphNodeData; GNode, GOwner> = class(TutlGraphNode) + private type + TGenericNodeEnumerator = class(TObject) + private + fOwner: TutlGraphNode; + fPos: Integer; + function GetCurrent: GNode; + public + property Current: GNode read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aOwner: TutlGraphNode); + end; + private + function GetParent: GNode; + function GetOwner: GOwner; + function GetData: GData; + function GetItemsGeneric(const aIndex: Integer): GNode; + public + property Parent: GNode read GetParent; + property Owner: GOwner read GetOwner; + property Data: GData read GetData; + property Items[const aIndex: Integer]: GNode read GetItemsGeneric; default; + + function AddItem: GNode; + function IndexOf(const aItem: GNode): Integer; + function IsParent(const aNode: GNode): Boolean; + function Move(const aParent: GNode): Boolean; + + function GetEnumerator: TGenericNodeEnumerator; + + constructor Create(const aParent: GNode; const aOwner: GOwner); + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + generic TutlGenericGraph<T: TutlGraphNode> = class(TutlGraph) + private + function GetRootNode: T; + public + property RootNode: T read GetRootNode; + end; + +implementation + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGraphNodeData///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlGraphNodeData.Create; +begin + inherited Create; + //nothing to do here +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGraphNode.TNodeEnumerator////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.TNodeEnumerator.GetCurrent: TutlGraphNode; +begin + result := fOwner.Items[fPos]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.TNodeEnumerator.MoveNext: Boolean; +begin + inc(fPos); + result := (fPos < fOwner.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlGraphNode.TNodeEnumerator.Create(const aOwner: TutlGraphNode); +begin + inherited Create; + fPos := -1; + fOwner := aOwner; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGraphNode///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlGraphNode.GetDataClass: TutlGraphNodeDataClass; +begin + result := TutlGraphNodeData; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.GetCount: Integer; +begin + result := fItems.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.GetItems(const aIndex: Integer): TutlGraphNode; +begin + if (aIndex >= 0) and (aIndex < Count) then + result := (fItems[aIndex] as TutlGraphNode) + else + raise Exception.Create(Format('index (%d) is out of Range (%d - %d)', [aIndex, 0, Count-1])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.AttachNode(const aNode: TutlGraphNode): Boolean; +begin + result := true; + fItems.Add(aNode); + aNode.fParent := self; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.DetachNode(const aNode: TutlGraphNode): Boolean; +var + i: Integer; +begin + result := false; + i := fItems.IndexOf(aNode); + if (i < 0) then + exit; + try + fItems.OwnsObjects := false; + fItems.Delete(i); + aNode.fParent := nil; + finally + fItems.OwnsObjects := true; + end; + result := true; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.AddItem: TutlGraphNode; +begin + if Assigned(Owner) then + result := Owner.GetItemClass().Create(self, Owner) + else + result := TutlGraphNode.Create(self, Owner); + fItems.Add(result); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.IndexOf(const aItem: TutlGraphNode): Integer; +begin + result := fItems.IndexOf(aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlGraphNode.DelItem(const aIndex: Integer); +begin + if (aIndex >= 0) and (aIndex < Count) then begin + fItems.Delete(aIndex); + end else + raise Exception.Create(Format('index (%d) is out of Range (%d - %d)', [aIndex, 0, Count-1])); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlGraphNode.Clear; +begin + fItems.Clear; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.IsParent(const aNode: TutlGraphNode): Boolean; +var + n: TutlGraphNode; +begin + n := self; + result := true; + while Assigned(n.Parent) do begin + if (aNode = n.Parent) then + exit; + n := n.Parent; + end; + result := false; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.Move(const aParent: TutlGraphNode): Boolean; +var + oldParent: TutlGraphNode; +begin + result := false; + if (aParent.IsParent(self)) then + exit; + oldParent := Parent; + if Assigned(oldParent) and not oldParent.DetachNode(self) then + exit; + if not aParent.AttachNode(self) then begin + if Assigned(oldParent) then + oldParent.AttachNode(self); + exit; + end; + result := true; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGraphNode.GetEnumerator: TNodeEnumerator; +begin + result := TNodeEnumerator.Create(self); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlGraphNode.Create(const aParent: TutlGraphNode; const aOwner: TutlGraph); +begin + inherited Create; + fParent := aParent; + fOwner := aOwner; + fData := GetDataClass().Create(); + fItems := TObjectList.create(true); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlGraphNode.Destroy; +begin + FreeAndNil(fData); + FreeAndNil(fItems); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGraph///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +class function TutlGraph.GetItemClass: TutlGraphNodeClass; +begin + result := TutlGraphNode; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlGraph.Create; +begin + inherited Create; + fRootNode := GetItemClass().Create(nil, self); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlGraph.Destroy; +begin + FreeAndNil(fRootNode); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGenericGraphNode.TGenericNodeEnumerator/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.TGenericNodeEnumerator.GetCurrent: GNode; +begin + result := GNode(fOwner.Items[fPos]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.TGenericNodeEnumerator.MoveNext: Boolean; +begin + inc(fPos); + result := (fPos < fOwner.Count); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlGenericGraphNode.TGenericNodeEnumerator.Create(const aOwner: TutlGraphNode); +begin + inherited Create; + fPos := -1; + fOwner := aOwner; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGenericGraphNode////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.GetParent: GNode; +begin + result := GNode(fParent); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.GetOwner: GOwner; +begin + result := GOwner(fOwner); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.GetData: GData; +begin + result := GData(fData); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.GetItemsGeneric(const aIndex: Integer): GNode; +begin + result := GNode(inherited GetItems(aIndex)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.AddItem: GNode; +begin + result := GNode(inherited AddItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.IndexOf(const aItem: GNode): Integer; +begin + result := inherited IndexOf(aItem); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.IsParent(const aNode: GNode): Boolean; +begin + result := inherited IsParent(aNode); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.Move(const aParent: GNode): Boolean; +begin + result := inherited Move(aParent); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraphNode.GetEnumerator: TGenericNodeEnumerator; +begin + result := TGenericNodeEnumerator.Create(self); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlGenericGraphNode.Create(const aParent: GNode; const aOwner: GOwner); +begin + inherited Create(TutlGraphNode(aParent), TutlGraph(aOwner)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlGenericGraph////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlGenericGraph.GetRootNode: T; +begin + result := (fRootNode as T); +end; + +end. + diff --git a/uutlKeyCodes.pas b/uutlKeyCodes.pas new file mode 100644 index 0000000..e602911 --- /dev/null +++ b/uutlKeyCodes.pas @@ -0,0 +1,263 @@ +unit uutlKeyCodes; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält alle virtuellen Key Codes } + +{$mode objfpc}{$H+} + +interface + +uses Classes; + +{$REGION SCANCODES} +const + VK_UNKNOWN = 0; // defined by LCL + VK_LBUTTON = 1; + VK_RBUTTON = 2; + VK_CANCEL = 3; + VK_MBUTTON = 4; + VK_XBUTTON1 = 5; + VK_XBUTTON2 = 6; + VK_BACK = 8; // The "Backspace" key, dont confuse with the + // Android BACK key which is mapped to VK_ESCAPE + VK_TAB = 9; + VK_CLEAR = 12; + VK_RETURN = 13; // The "Enter" key, also used for a keypad center press + VK_SHIFT = 16; // See also VK_LSHIFT, VK_RSHIFT + VK_CONTROL = 17; // See also VK_LCONTROL, VK_RCONTROL + VK_MENU = 18; + // The ALT key. Also called "Option" in Mac OS X. See also VK_LMENU, VK_RMENU + VK_PAUSE = 19; // Pause/Break key + VK_CAPITAL = 20; // CapsLock key + VK_KANA = 21; + VK_HANGUL = 21; + VK_JUNJA = 23; + VK_FINAL = 24; + VK_HANJA = 25; + VK_KANJI = 25; + VK_ESCAPE = 27; // Also used for the hardware Back key in Android + VK_CONVERT = 28; + VK_NONCONVERT = 29; + VK_ACCEPT = 30; + VK_MODECHANGE = 31; + VK_SPACE = 32; + VK_PRIOR = 33; // Page Up + VK_NEXT = 34; // Page Down + VK_END = 35; + VK_HOME = 36; + VK_LEFT = 37; + VK_UP = 38; + VK_RIGHT = 39; + VK_DOWN = 40; + VK_SELECT = 41; + VK_PRINT = 42; // PrintScreen key + VK_EXECUTE = 43; + VK_SNAPSHOT = 44; + VK_INSERT = 45; + VK_DELETE = 46; + VK_HELP = 47; + VK_0 = $30; + VK_1 = $31; + VK_2 = $32; + VK_3 = $33; + VK_4 = $34; + VK_5 = $35; + VK_6 = $36; + VK_7 = $37; + VK_8 = $38; + VK_9 = $39; + //3A-40 Undefined + VK_A = $41; + VK_B = $42; + VK_C = $43; + VK_D = $44; + VK_E = $45; + VK_F = $46; + VK_G = $47; + VK_H = $48; + VK_I = $49; + VK_J = $4A; + VK_K = $4B; + VK_L = $4C; + VK_M = $4D; + VK_N = $4E; + VK_O = $4F; + VK_P = $50; + VK_Q = $51; + VK_R = $52; + VK_S = $53; + VK_T = $54; + VK_U = $55; + VK_V = $56; + VK_W = $57; + VK_X = $58; + VK_Y = $59; + VK_Z = $5A; + + VK_LWIN = $5B; + // In Mac OS X this is the Apple, or Command key. Windows Key in PC keyboards + VK_RWIN = $5C; + // In Mac OS X this is the Apple, or Command key. Windows Key in PC keyboards + VK_APPS = $5D; // The PopUp key in PC keyboards + // $5E reserved + VK_SLEEP = $5F; + + VK_NUMPAD0 = 96; // $60 + VK_NUMPAD1 = 97; + VK_NUMPAD2 = 98; + VK_NUMPAD3 = 99; + VK_NUMPAD4 = 100; + VK_NUMPAD5 = 101; + VK_NUMPAD6 = 102; + VK_NUMPAD7 = 103; + VK_NUMPAD8 = 104; + VK_NUMPAD9 = 105; + VK_MULTIPLY = 106; + // VK_MULTIPLY up to VK_DIVIDE are usually in the numeric keypad in PC keyboards + VK_ADD = 107; + VK_SEPARATOR = 108; + VK_SUBTRACT = 109; + VK_DECIMAL = 110; + VK_DIVIDE = 111; + VK_F1 = 112; + VK_F2 = 113; + VK_F3 = 114; + VK_F4 = 115; + VK_F5 = 116; + VK_F6 = 117; + VK_F7 = 118; + VK_F8 = 119; + VK_F9 = 120; + VK_F10 = 121; + VK_F11 = 122; + VK_F12 = 123; + VK_F13 = 124; + VK_F14 = 125; + VK_F15 = 126; + VK_F16 = 127; + VK_F17 = 128; + VK_F18 = 129; + VK_F19 = 130; + VK_F20 = 131; + VK_F21 = 132; + VK_F22 = 133; + VK_F23 = 134; + VK_F24 = 135; // $87 + // $88-$8F unassigned + VK_NUMLOCK = $90; + VK_SCROLL = $91; + +{$ENDREGION} + +function CharCodeToVKCode(Ch: WideChar; out shift: TShiftState): word; +function VKCodeToCharCode(key: word; Shift: TShiftState): WideChar; + +implementation + +{$IFDEF WINDOWS} + +uses Windows; + +function CharCodeToVKCode(Ch: WideChar; out shift: TShiftState): word; +var + st: SmallInt; +begin + shift:= []; + Result:= 0; + if ch=#0 then + exit; + st:= VkKeyScan(AnsiChar(UnicodeChar(ch))); + if (hi(st)=$FF) and (lo(st)=$FF) then + exit; + + Result:= lo(st); + if Result and (1 shl 8) > 0 then include(shift, ssShift); + if Result and (2 shl 8) > 0 then include(shift, ssCtrl); + if Result and (4 shl 8) > 0 then include(shift, ssAlt); + if [ssCtrl, ssAlt] - shift = [] then + include(shift, ssAltGr); +end; + +function VKCodeToCharCode(key: word; Shift: TShiftState): WideChar; +var + sc: word; + ks: array[0..255] of byte; + buf: array[0..1] of AnsiChar; +begin + Result:= #0; + sc:= MapVirtualKey(key, {MAPVK_VK_TO_VSC} 0); + FillChar({%H-}ks[0], sizeof(ks), 0); + if ssShift in Shift then ks[VK_SHIFT]:= $80; + if ssCtrl in Shift then ks[VK_CONTROL]:= $80; + if ssAlt in Shift then ks[VK_MENU]:= $80; + if ssCaps in Shift then ks[VK_CAPITAL]:= $81; + + buf:= #0#0; + case ToAscii(key, sc, @ks[0], LPWORD(@buf[0]), 0) of + 0: Result:= #0;//The specified virtual key has no translation for the current state of the keyboard. + 1: Result:= UnicodeChar(AnsiChar(buf[0]));//One character was copied to the buffer + 2: Result:= UnicodeChar(AnsiChar(buf[1]));//Two characters were copied to the buffer. This usually happens when a dead-key character (accent or diacritic) stored in the keyboard layout cannot be composed with the specified virtual key to form a single character. + end; +end; + +{$ELSE} + +uses SysUtils, gtk2proc; + +function VKCodeToCharCode(key: word; Shift: TShiftState): WideChar; +var + vki: TVKeyInfo; + dt: PAnsiChar; +begin + Result:= #0; + vki:= GetVKeyInfo(Key); + if strlen(vki.KeyChar[0])>0 then begin + dt:= ''; + if []=Shift then + dt:= vki.KeyChar[0] + else + if ([ssShift]=Shift) or ([ssCaps]=Shift) then + dt:= vki.KeyChar[1] + else + if ([ssCtrl, ssAlt]=Shift) or ([ssAltGr]=Shift) then + dt:= vki.KeyChar[2]; + Utf8ToUnicode(@Result, 1, PChar(dt), strlen(dt)); + end; +end; + +function CharCodeToVKCode(Ch: WideChar; out shift: TShiftState): word; +var + k: Word; + vki: TVKeyInfo; + utf8ch: array[0..high(TVKeyUTF8Char)] of AnsiChar; +begin + Result:= 0; + if ch=#0 then + exit; + utf8ch:= #0#0#0#0#0#0#0#0; //wat + UnicodeToUTF8(@utf8ch[0], sizeof(utf8ch), @ch, 1); + for k:= low(byte) to high(byte) do begin + vki:= GetVKeyInfo(k); + if CompareMem(@utf8ch, @vki.KeyChar[0], sizeof(utf8ch)) then begin + Result:= k; + shift:= []; + exit; + end else + if CompareMem(@utf8ch, @vki.KeyChar[1], sizeof(utf8ch)) then begin + Result:= k; + shift:= [ssShift]; + exit; + end else + if CompareMem(@utf8ch, @vki.KeyChar[2], sizeof(utf8ch)) then begin + Result:= k; + shift:= [ssAltGr, ssAlt, ssCtrl]; + exit; + end; + end; +end; + +{$ENDIF} + + +end. \ No newline at end of file diff --git a/uutlLocalization.pas b/uutlLocalization.pas new file mode 100644 index 0000000..b18e8db --- /dev/null +++ b/uutlLocalization.pas @@ -0,0 +1,244 @@ +unit uutlLocalization; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit stellt Mechanismen zur Übersetzung von Texten mit Hilfe von PO/MO-Files zur Verfügung } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, gettext, + uutlCommon, uutlGenerics, uutlStreamHelper; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlLocalizationItem = class(TObject) + Name, Comment: String; + constructor Create; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlLocalizationDatabase = class(TObject) + private type + TStringObjMap = specialize TutlMap<String, TutlLocalizationItem>; + private + fStringList: TStringObjMap; + fLangFile: TMOFile; + + function GetCount: Integer; + function GetObject(Index: Integer): TutlLocalizationItem; + public + property Count : Integer read GetCount; + property Objects[Index: Integer]: TutlLocalizationItem read GetObject; + function AddName(const Name, Comment: string): TutlLocalizationItem; + function RemoveName(const Name: string): boolean; + + procedure LoadFromStream(const aStream: TStream); + procedure SaveToStream(const aStream: TStream); + + procedure LoadLanguage(const aStream: TStream); + function Translate(const Name: string): string; + + constructor Create; + destructor Destroy; override; + end; + +function utlLocalizationDatabase: TutlLocalizationDatabase; +function __(Name: string; Default: string = #0): string; overload; + +implementation + +uses + Dialogs, uvfsManager; + +var + Entity: TutlLocalizationDatabase; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlLocalizationDatabase: TutlLocalizationDatabase; +var + str: IStreamHandle; +begin + if not Assigned(Entity) then begin + Entity := TutlLocalizationDatabase.Create; + if vfsManager.ReadFile('lang/strings', str) then + Entity.LoadFromStream(str.GetStream); + end; + result := Entity; +end; + + +function __(Name: string; Default: string): string; +begin + if Default=#0 then + Default := Name; + Result := utlLocalizationDatabase.Translate(Name); + if (Result = '') then + Result := Default; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlLocalizationItem//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//erstellt das Objekt +constructor TutlLocalizationItem.Create; +begin + inherited Create; + Name := ''; + Comment := ''; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlLocalizationDatabase/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Get-Methode der Count-Eigenschaft +function TutlLocalizationDatabase.GetCount: Integer; +begin + result := fStringList.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//Get-Methode der Value-Eigenschaft +function TutlLocalizationDatabase.GetObject(Index: Integer): TutlLocalizationItem; +begin + if (Index >= 0) and (Index < fStringList.Count) then + result := fStringList.ValueAt[Index] + else + result := nil; +end; + +function TutlLocalizationDatabase.AddName(const Name, Comment: string): TutlLocalizationItem; +var + e: TutlLocalizationItem; + i: Integer; +begin + i := fStringList.IndexOf(Name); + if i >= 0 then + Result:= Objects[i] + else begin + e := TutlLocalizationItem.Create; + e.Name := Name; + e.Comment := Comment; + fStringList.Add(e.Name, e); + result := e; + end; +end; + +function TutlLocalizationDatabase.RemoveName(const Name: string): boolean; +begin + Result:= fStringList.IndexOf(Name) >= 0; + if Result then + fStringList.Delete(Name); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//läd die Datenbank aus einem Stream +//@aStream: Stream aus der geladen werden soll; +procedure TutlLocalizationDatabase.LoadFromStream(const aStream: TStream); +const + HEADER = 'StringDatabase'; +var + rd: TutlStreamReader; + csv: TutlCSVList; + co: string; +begin + rd:= TutlStreamReader.Create(aStream); + try + if HEADER <> rd.ReadLine then + raise Exception.Create('TStringDatabase.LoadFromStream - invalid Stream'); + fStringList.Clear; + csv:= TutlCSVList.Create; + try + csv.Delimiter:= ';'; + csv.StrictDelimitedText:= rd.ReadLine; + while csv.Count>=1 do begin + co:= ''; + if csv.Count>1 then + co:= csv[1]; + AddName(csv[0],co); + // next line + csv.StrictDelimitedText:= rd.ReadLine; + end; + finally + FreeAndNil(csv); + end; + finally + FreeAndNil(rd); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//speichert die Datenbank in einem Stream +//@aStream: Stream in dem gespeichert werden soll; +procedure TutlLocalizationDatabase.SaveToStream(const aStream: TStream); +const + HEADER = 'StringDatabase'; +var + i: Integer; + wr: TutlStreamWriter; + csv: TutlCSVList; + o: TutlLocalizationItem; +begin + wr:= TutlStreamWriter.Create(aStream); + try + wr.WriteLine(HEADER); + csv:= TutlCSVList.Create; + try + csv.Delimiter:= ';'; + for i := 0 to fStringList.Count-1 do begin + csv.Clear; + o:= Objects[i]; + csv.Add(o.Name); + csv.Add(o.Comment); + wr.WriteLine(csv.StrictDelimitedText); + end; + finally + FreeAndNil(csv); + end; + finally + FreeAndNil(wr); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlLocalizationDatabase.LoadLanguage(const aStream: TStream); +begin + fLangFile.Free; + fLangFile := nil; + fLangFile := TMOFile.Create(aStream); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlLocalizationDatabase.Translate(const Name: string): string; +begin + if Assigned(fLangFile) then + Result := UTF8Encode(fLangFile.Translate(Name)) + else + Result := ''; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//erstellt das Objekt +constructor TutlLocalizationDatabase.Create; +begin + inherited Create; + fStringList := TStringObjMap.Create(True); + fLangFile := nil; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//gibt das Objekt frei +destructor TutlLocalizationDatabase.Destroy; +begin + fLangFile.Free; + fStringList.Free; + inherited Destroy; +end; + +finalization + FreeAndNil(Entity); + +end. + diff --git a/uutlLogger.pas b/uutlLogger.pas new file mode 100644 index 0000000..8ae9b3c --- /dev/null +++ b/uutlLogger.pas @@ -0,0 +1,436 @@ +unit uutlLogger; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält das Logging-Framework + +Anzusprechen über Singleton: utlLogger + +Die einzelnen Level sind über die Methoden Debug(), Info(), Warning(), Error() zugänglich. +Sender: entweder eigener Text oder TObject-Referenz, dann wird Klassenname und Adresse ausgegeben. + +Log-Zeilen werden nicht weiter behandelt, sondern an Consumer verteilt. +Es können beliebig viele Consumer per RegisterConsumer für bestimmte Level registriert werden. +Jeder davon bekommt die Rohdaten eines Logeintrags auf einer beobachteten Stufe. +Zum einfacheren Ausgeben gibt es eine Hilfsfunktion FormatLine vom Logger. + +Vordefinierte Consumer: + TutlFileLogger - schreibt in eine Datei + TutlConsoleLogger - schreibt auf die Konsole (ggf. mit CriticalSection) + TutlEventLogger - ruft beliebiges Event auf +} + +{$mode objfpc}{$H+} + +interface + +uses + {$IFDEF MSWINDOWS}Windows{$ELSE}unix{$ENDIF}, + Classes, SysUtils, uutlGenerics, syncobjs, uutlCommon; + +type + TutlLogLevel = (llDebug, llInfo, llWarning, llError); + TutlLogLevels = set of TutlLogLevel; + +const + utlLogLevel_Any = [low(TutlLogLevel)..high(TutlLogLevel)]; + utlLogLevel_NoDebug = utlLogLevel_Any - [llDebug]; + utlLogLevelStrings: array[TutlLogLevel] of string = + ('Debug','Info','Warning','Error'); + +type + TutlLogger = class; + IutlLogConsumer = interface(IUnknown) + procedure WriteLog(const aLogger: TutlLogger; const aTime:TDateTime; const aLevel:TutlLogLevel; const aSender: string; const aMessage: String); + end; + + TutlLogConsumerList = specialize TutlInterfaceList<IutlLogConsumer>; + + { TutlLogger } + + TutlLogger = class(TObject) + private + fConsumersLock: TCriticalSection; + fConsumers: array[TutlLogLevel] of TutlLogConsumerList; + protected + class function FormatTime(const aTime:TDateTime): string; + function FormatSender(const aSender: TObject): String; + procedure InternalLog(const aLevel:TutlLogLevel; const aSender: TObject; const aMessage: String; const aParams: array of const); overload; + procedure InternalLog(const aLevel:TutlLogLevel; const aSender: String; const aMessage: String; const aParams: array of const); overload; + public + procedure RegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter:TutlLogLevels=utlLogLevel_Any); + procedure UnRegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter:TutlLogLevels=utlLogLevel_Any); + + class function FormatLine(const aTime:TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String): string; + + procedure Debug(const aSender: TObject; const aMessage: String; const aParams: array of const); overload; + procedure Debug(const aSender: String; const aMessage: String; const aParams: array of const); overload; + procedure Log(const aSender: TObject; const aMessage: String; const aParams: array of const); overload; + procedure Log(const aSender: String; const aMessage: String; const aParams: array of const); overload; + procedure Warning(const aSender: TObject; const aMessage: String; const aParams: array of const); overload; + procedure Warning(const aSender: String; const aMessage: String; const aParams: array of const); overload; + procedure Error(const aSender: TObject; const aMessage: String; const aParams: array of const); overload; + procedure Error(const aSender: String; const aMessage: String; const aParams: array of const); overload; + procedure Error(const aSender: String; const aMessage: String; const aException: Exception); overload; + procedure Error(const aSender: TObject; const aMessage: String; const aException: Exception); overload; + + constructor Create; + destructor Destroy; override; + end; + + { TutlFileLogger } + + TutlFileLoggerMode = (flmCreateNew, flmAppend); + TutlFileLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer) + private + fStream: TFileStream; + fAutoFlush: boolean; + protected + procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); + public + constructor Create(const aFilename: String; const aMode: TutlFileLoggerMode); + destructor Destroy; override; + + procedure Flush(); overload; + published + property AutoFlush:boolean read fAutoFlush write fAutoFlush; + end; + + { TutlConsoleLogger } + + TutlConsoleLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer) + private + fFreeConsoleCS: boolean; + fConsoleCS: TCriticalSection; + fOnBeforeLog: TNotifyEvent; + fOnAfterLog: TNotifyEvent; + protected + procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); virtual; + public + property ConsoleCS: TCriticalSection read fConsoleCS; + property OnBeforeLog: TNotifyEvent read fOnBeforeLog write fOnBeforeLog; + property OnAfterLog: TNotifyEvent read fOnAfterLog write fOnAfterLog; + + constructor Create(const aSection: TCriticalSection = nil); + destructor Destroy; override; + end; + + { TutlEventLogger } + TutlWriteLogEvent = procedure (const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String) of object; + TutlEventLogger = class(TutlInterfaceNoRefCount, IutlLogConsumer) + private + fWriteLogEvt: TutlWriteLogEvent; + protected + procedure WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); + public + constructor Create(const aEvent: TutlWriteLogEvent); + end; + +function utlLogger: TutlLogger; +function utlCreateStackTrace(const aMessage: String; const aException: Exception): String; + +implementation + +var + utlLogger_Singleton: TutlLogger; + +function utlLogger: TutlLogger; +begin + if not Assigned(utlLogger_Singleton) then + utlLogger_Singleton:= TutlLogger.Create; + Result:= utlLogger_Singleton; +end; + +function utlCreateStackTrace(const aMessage: String; const aException: Exception): String; +var + i: Integer; + frames: PPointer; +begin + result := aMessage; + if Assigned(aException) then + result := result + sLineBreak + + ' Exception: ' + aException.ClassName + sLineBreak + + ' Message: ' + aException.Message + sLineBreak + + ' StackTrace:' + sLineBreak + + ' ' + BackTraceStrFunc(ExceptAddr) + else + result := result + 'no Exception passed'; + frames := ExceptFrames; + for i := 0 to ExceptFrameCount-1 do + result := result + sLineBreak + ' ' + BackTraceStrFunc(frames[i]); +end; + +{ TutlFileLogger } + +function FileFlush(Handle: THandle): Boolean; +begin + {$IFDEF MSWINDOWS} + Result:= FlushFileBuffers(Handle); + {$ELSE} + Result:= (fpfsync(Handle) = 0); + {$ENDIF} +end; + +procedure TutlFileLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; + const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); +var + buf: AnsiString; +begin + if Assigned(fStream) then begin + buf:= aLogger.FormatLine(aTime, aLevel, aSender, aMessage)+sLineBreak; + fStream.Write(buf[1], Length(buf)); + if AutoFlush then + FileFlush(fStream.Handle); + end; +end; + +constructor TutlFileLogger.Create(const aFilename: String; const aMode: TutlFileLoggerMode); +const + RIGHTS: Cardinal = {$IFNDEF UNIX}fmShareDenyWrite{$ELSE}%0100100100 {-r--r--r--}{$ENDIF}; +begin + try + if (aMode = flmCreateNew) or not FileExists(aFilename) then begin + if FileExists(aFilename) then + DeleteFile(aFilename); + fStream := TFileStream.Create(aFilename, fmCreate{$IFNDEF UNIX} or RIGHTS{$ENDIF}, RIGHTS) + end else + fStream := TFileStream.Create(aFilename, fmOpenReadWrite{$IFNDEF UNIX} or RIGHTS{$ENDIF}, RIGHTS); + fStream.Position := fStream.Size; + except + on e: EStreamError do begin + fStream:= nil; + utlLogger.Error('Logger', 'Could not open log file "%s"',[aFilename]); + end else + raise; + end; + AutoFlush:=true; +end; + +destructor TutlFileLogger.Destroy; +begin + FreeAndNil(fStream); + inherited Destroy; +end; + +procedure TutlFileLogger.Flush; +begin + if Assigned(fStream) then + FileFlush(fStream.Handle); +end; + +{ TutlConsoleLogger } + +procedure TutlConsoleLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; + const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); +begin + fConsoleCS.Acquire; + try + if Assigned(fOnBeforeLog) then + fOnBeforeLog(Self); + WriteLn(aLogger.FormatLine(aTime, aLevel, aSender, aMessage)); + if Assigned(fOnAfterLog) then + fOnAfterLog(Self); + finally + fConsoleCS.Release; + end; +end; + +constructor TutlConsoleLogger.Create(const aSection: TCriticalSection); +begin + inherited Create; + if Assigned(aSection) then + fConsoleCS:= aSection + else + fConsoleCS:= TCriticalSection.Create; + fFreeConsoleCS:= not Assigned(aSection); +end; + +destructor TutlConsoleLogger.Destroy; +begin + if fFreeConsoleCS then + FreeAndNil(fConsoleCS); + inherited Destroy; +end; + +{ TutlEventLogger } + +procedure TutlEventLogger.WriteLog(const aLogger: TutlLogger; const aTime: TDateTime; + const aLevel: TutlLogLevel; const aSender: string; const aMessage: String); +begin + fWriteLogEvt(aLogger,aTime, aLevel, aSender, aMessage); +end; + +constructor TutlEventLogger.Create(const aEvent: TutlWriteLogEvent); +begin + inherited Create; + fWriteLogEvt:= aEvent; +end; + +{ TutlLogger } + +procedure TutlLogger.RegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter: TutlLogLevels); +var + ll: TutlLogLevel; +begin + fConsumersLock.Acquire; + try + for ll:= low(ll) to high(ll) do + if (ll in aFilter) and (fConsumers[ll].IndexOf(aConsumer)<0) then + fConsumers[ll].Add(aConsumer); + finally + fConsumersLock.Release; + end; + if llDebug in aFilter then + aConsumer.WriteLog(Self, Now, llDebug, 'System', 'Attached to Logger'); +end; + +procedure TutlLogger.UnRegisterConsumer(const aConsumer: IutlLogConsumer; const aFilter: TutlLogLevels); +var + ll: TutlLogLevel; +begin + fConsumersLock.Acquire; + try + for ll:= low(ll) to high(ll) do + if ll in aFilter then + fConsumers[ll].Remove(aConsumer); + finally + fConsumersLock.Release; + end; +end; + +class function TutlLogger.FormatTime(const aTime: TDateTime): string; +begin + Result:= FormatDateTime('hh:nn:ss.zzz',aTime); +end; + +function TutlLogger.FormatSender(const aSender: TObject): String; +begin + if Assigned(aSender) then + result := format('%s[0x%P]', [aSender.ClassName, Pointer(aSender)]) + else + result := ''; +end; + +class function TutlLogger.FormatLine(const aTime: TDateTime; const aLevel: TutlLogLevel; const aSender: string; const aMessage: String): string; +begin + if (aSender <> '') then + Result:= Format('%s %-9s %s: %s', [FormatTime(aTime), UpperCase(utlLogLevelStrings[aLevel]), aSender, aMessage]) + else + Result:= Format('%s %-9s %s', [FormatTime(aTime), UpperCase(utlLogLevelStrings[aLevel]), aMessage]); +end; + +procedure TutlLogger.InternalLog(const aLevel: TutlLogLevel; const aSender: TObject; const aMessage: String; const aParams: array of const); +begin + InternalLog(aLevel, FormatSender(aSender), aMessage, aParams); +end; + +procedure TutlLogger.InternalLog(const aLevel: TutlLogLevel; const aSender: String; const aMessage: String; const aParams: array of const); +var + msg: string; + when: TDateTime; + i: integer; +begin + if length(aParams) = 0 then + msg:= aMessage + else + msg:= Format(aMessage, aParams); + when:= Now; + + fConsumersLock.Acquire; + try + for i:= 0 to fConsumers[aLevel].Count-1 do begin + fConsumers[aLevel][i].WriteLog(Self, when, aLevel, aSender, msg); + end; + finally + fConsumersLock.Release; + end; +end; + +procedure TutlLogger.Debug(const aSender: TObject; const aMessage: String; const aParams: array of const); +begin + InternalLog(llDebug, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Debug(const aSender: String; const aMessage: String; const aParams: array of const); +begin + InternalLog(llDebug, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Log(const aSender: TObject; const aMessage: String; const aParams: array of const); +begin + InternalLog(llInfo, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Log(const aSender: String; const aMessage: String; const aParams: array of const); +begin + InternalLog(llInfo, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Warning(const aSender: TObject; const aMessage: String; const aParams: array of const); +begin + InternalLog(llWarning, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Warning(const aSender: String; const aMessage: String; const aParams: array of const); +begin + InternalLog(llWarning, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Error(const aSender: TObject; const aMessage: String; const aParams: array of const); +begin + InternalLog(llError, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Error(const aSender: String; const aMessage: String; const aParams: array of const); +begin + InternalLog(llError, aSender, aMessage, aParams); +end; + +procedure TutlLogger.Error(const aSender: String; const aMessage: String; const aException: Exception); +begin + InternalLog(llError, aSender, utlCreateStackTrace(aMessage, aException), []); +end; + +procedure TutlLogger.Error(const aSender: TObject; const aMessage: String; const aException: Exception); +begin + InternalLog(llError, aSender, utlCreateStackTrace(aMessage, aException), []); +end; + +constructor TutlLogger.Create; +var + ll: TutlLogLevel; +begin + inherited Create; + fConsumersLock:= TCriticalSection.Create; + fConsumersLock.Acquire; + try + for ll:= low(ll) to high(ll) do begin + fConsumers[ll]:= TutlLogConsumerList.Create; + end; + finally + fConsumersLock.Release; + end; +end; + +destructor TutlLogger.Destroy; +var + ll: TutlLogLevel; +begin + fConsumersLock.Acquire; + try + for ll:= low(ll) to high(ll) do begin + fConsumers[ll].Clear; + FreeAndNil(fConsumers[ll]); + end; + finally + fConsumersLock.Release; + end; + FreeAndNil(fConsumersLock); + inherited Destroy; +end; + +finalization + FreeAndNil(utlLogger_Singleton); + +end. + diff --git a/uutlMCF.pas b/uutlMCF.pas new file mode 100644 index 0000000..228bef0 --- /dev/null +++ b/uutlMCF.pas @@ -0,0 +1,645 @@ +unit uutlMCF; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält Klassen zum Lesen und Schreiben eines MuoConfgiFiles (kurz MCF) + + Lesen/Schreiben in/von Stream über TutlMCFFile + LineEndMode zur Kompatibilität mit MCF-alt und KCF: + leNone - Kein Semikolon erlaubt (KCF) + leAcceptNoWrite - Semikolon wird beim Lesen ignoriert, beim Schreiben weggelassen + leAlways - Beim Lesen erforderlich, immer geschrieben (MCF-alt) + + Jeder SectionName und jeder ValueName ist Unique, es kann aber ein Value und eine + Section mit dem gleichen Namen existieren + + Zugriff auf Subsections über .Section(), mehrere Stufen auf einmal mit . getrennt: + mcf.Section('foo.bar.baz') === mcf.Section('foo').Section('bar').Section('baz') + Zugriff erstellt automatisch eine Section, falls sie nicht existiert. Prüfung mit + SectionExists (nur direkt, keine Pfade!). + + Zugriff auf Werte von der Section aus: + Get/Set[Int,Float,String,Bool](Key, Default) + ValueExists() + UnsetValue() + Strings sind Widestrings, Un/Escaping passiert beim Dateizugriff automatisch + + Enumeration: ValueCount/ValueNameAt, SectionCount/SectionNameAt } + +interface + +uses + SysUtils, Classes, uutlStreamHelper; + +type + EConfigException = class(Exception) + end; + TutlMCFSection = class; + TutlMCFFile = class; + TutlMCFLineEndMarkerMode = (leNone, leAcceptNoWrite, leAlways); + + { TutlMCFSection } + + TutlMCFSection = class + private type + TSectionEnumerator = class(TObject) + private + fList: TStringList; + fPosition: Integer; + function GetCurrent: TutlMCFSection; + public + property Current: TutlMCFSection read GetCurrent; + function MoveNext: Boolean; + constructor Create(const aList: TStringList); + end; + private + FSections, + FValues: TStringList; + function GetSection(aPath: String): TutlMCFSection; + function GetSectionCount: integer; + function GetSectionName(Index: integer): string; + function GetSectionByIndex(aIndex: Integer): TutlMCFSection; + function GetValueCount: integer; + function GetValueName(Index: integer): string; + protected + procedure ClearSections; + procedure ClearValues; + procedure SaveData(Stream: TStream; Indent: string; LineEnds: TutlMCFLineEndMarkerMode); + procedure LoadData(Data: TStream; LineEnds: TutlMCFLineEndMarkerMode; Depth: Integer); + procedure AddValueChecked(Name: String; Val: TObject); + procedure SplitPath(const Path: String; out First, Rest: String); + public + constructor Create; + destructor Destroy; override; + + function GetEnumerator: TSectionEnumerator; + + property ValueCount: integer read GetValueCount; + property ValueNameAt[Index: integer]: string read GetValueName; + + property SectionCount: integer read GetSectionCount; + property SectionNameAt[Index: integer]: string read GetSectionName; + property Sections[aPath: String]: TutlMCFSection read GetSection; default; + property SectionByIndex[aIndex: Integer]: TutlMCFSection read GetSectionByIndex; + + function SectionExists(Path: string): boolean; + function Section(Path: string): TutlMCFSection; + procedure DeleteSection(Name: string); + + function ValueExists(Name: string): boolean; + function GetInt(Name: string; Default: Int64 = 0): Int64; overload; + function GetFloat(Name: string; Default: Double = 0): Double; overload; + function GetString(Name: string; Default: AnsiString = ''): AnsiString; overload; + function GetStringW(Name: string; Default: UnicodeString = ''): UnicodeString; overload; + function GetBool(Name: string; Default: Boolean = false): Boolean; overload; + procedure SetInt(Name: string; Value: Int64); overload; + procedure SetFloat(Name: string; Value: Double); overload; + procedure SetString(Name: string; Value: WideString); overload; + procedure SetString(Name: string; Value: AnsiString); overload; + procedure SetBool(Name: string; Value: Boolean); overload; + procedure UnsetValue(Name: string); + end; + + { TutlMCFFile } + + TutlMCFFile = class(TutlMCFSection) + private + fLineEndMode: TutlMCFLineEndMarkerMode; + public + constructor Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode = leAcceptNoWrite); + procedure LoadFromStream(Stream: TStream); + procedure SaveToStream(Stream: TStream); + end; + +implementation + +uses Variants, StrUtils; + +const + sComment = '#'; + sSectionEnd = 'end'; + sSectionMarker = ':'; + sSectionPathDelim = '.'; + sLineEndMarker = ';'; + sValueDelim = '='; + sValueQuote = ''''; + sValueDecimal = '.'; + sIndentOnSave = ' '; + sNameValidChars = [' ' .. #$7F] - [sValueDelim]; + sWhitespaceChars = [#0 .. ' ']; + +type + StoredValue = Variant; + + { TutlMCFValue } + + TutlMCFValue = class + private + Format: TFormatSettings; + FValue: StoredValue; + procedure SetValue(const Value: StoredValue); + protected + function CheckSpecialChars(Data: WideString): boolean; + procedure LoadData(Data: string); + function SaveData: string; + class function Escape(Value: WideString): AnsiString; + class function Unescape(Value: AnsiString): WideString; + public + constructor Create(Val: StoredValue); + property Value: StoredValue read FValue write SetValue; + end; + + { TkcfValue } + +constructor TutlMCFValue.Create(Val: StoredValue); +begin + inherited Create; + SetValue(Val); + Format.DecimalSeparator:= sValueDecimal; +end; + +procedure TutlMCFValue.SetValue(const Value: StoredValue); +begin + FValue:= Value; +end; + +function TutlMCFValue.CheckSpecialChars(Data: WideString): boolean; +var + i: Integer; +begin + result := false; + for i:= 1 to Length(Data) do + if Data[i] in [sSectionMarker, sValueQuote, sValueDelim, sLineEndMarker, ' '] then + exit; + result := true; +end; + +procedure TutlMCFValue.LoadData(Data: string); +var + b: boolean; + i: int64; + d: double; + p: PChar; +begin + if TryStrToInt64(Data, i) then + Value:= i + else if TryStrToFloat(Data, d, Format) then + Value:= d + else if TryStrToBool(Data, b) then + Value:= b + else begin + p:= PChar(Data); + if p^ = sValueQuote then + Data := AnsiExtractQuotedStr(p, sValueQuote); + Value:= Unescape(Trim(Data)); + end; +end; + +function TutlMCFValue.SaveData: string; +begin + if VarIsType(FValue, varBoolean) then + Result:= BoolToStr(FValue, false) + else if VarIsType(FValue, varInt64) then + Result:= IntToStr(FValue) + else if VarIsType(FValue, varDouble) then + Result:= FloatToStr(Double(FValue), Format) + else begin + Result:= Escape(FValue); + if not CheckSpecialChars(WideString(Result)) then + Result:= AnsiQuotedStr(Result, sValueQuote); + end; +end; + +class function TutlMCFValue.Escape(Value: WideString): AnsiString; +var + i: integer; + wc: WideChar; +begin + Result:= ''; + for i:= 1 to length(Value) do begin + wc:= Value[i]; + case Ord(wc) of + Ord('\'), + $007F..$FFFF: Result:= Result + '\'+IntToHex(ord(wc),4); + else + Result:= Result + AnsiChar(wc); + end; + end; +end; + +class function TutlMCFValue.Unescape(Value: AnsiString): WideString; +var + i: integer; + c: Char; +begin + Result:= ''; + i:= 1; + while i <= length(value) do begin + c:= Value[i]; + if c='\' then begin + Result:= Result + WideChar(StrToInt('$'+Copy(Value,i+1,4))); + inc(i, 4); + end else + Result:= Result + WideChar(c); + inc(i); + end; +end; + +{ TutlMCFSection.TSectionEnumerator } + +function TutlMCFSection.TSectionEnumerator.GetCurrent: TutlMCFSection; +begin + result := TutlMCFSection(fList.Objects[fPosition]); +end; + +function TutlMCFSection.TSectionEnumerator.MoveNext: Boolean; +begin + inc(fPosition); + result := (fPosition < fList.Count); +end; + +constructor TutlMCFSection.TSectionEnumerator.Create(const aList: TStringList); +begin + inherited Create; + fList := aList; + fPosition := -1; +end; + +{ TkcfCompound } + +constructor TutlMCFSection.Create; +begin + inherited; + FSections:= TStringList.Create; + FSections.CaseSensitive:= false; + FSections.Sorted:= true; + FSections.Duplicates:= dupError; + FValues:= TStringList.Create; + FValues.CaseSensitive:= false; + FValues.Sorted:= true; + FValues.Duplicates:= dupError; +end; + +destructor TutlMCFSection.Destroy; +begin + ClearSections; + ClearValues; + FreeAndNil(FSections); + FreeAndNil(FValues); + inherited; +end; + +function TutlMCFSection.GetEnumerator: TSectionEnumerator; +begin + result := TSectionEnumerator.Create(FSections); +end; + +function TutlMCFSection.GetSectionCount: integer; +begin + Result:= FSections.Count; +end; + +function TutlMCFSection.GetSection(aPath: String): TutlMCFSection; +begin + result := Section(aPath); +end; + +function TutlMCFSection.GetSectionByIndex(aIndex: Integer): TutlMCFSection; +begin + result := (FSections.Objects[aIndex] as TutlMCFSection); +end; + +function TutlMCFSection.GetSectionName(Index: integer): string; +begin + Result:= FSections[Index]; +end; + +function TutlMCFSection.GetValueCount: integer; +begin + Result:= FValues.Count; +end; + +function TutlMCFSection.GetValueName(Index: integer): string; +begin + Result:= FValues[Index]; +end; + +procedure TutlMCFSection.ClearSections; +var + i: integer; +begin + for i:= FSections.Count - 1 downto 0 do + FSections.Objects[i].Free; + FSections.Clear; +end; + +procedure TutlMCFSection.ClearValues; +var + i: integer; +begin + for i:= FValues.Count - 1 downto 0 do + FValues.Objects[i].Free; + FValues.Clear; +end; + +procedure TutlMCFSection.SplitPath(const Path: String; out First, Rest: String); +begin + First:= Copy(Path, 1, Pos(sSectionPathDelim, Path)-1); + if First='' then begin + First:= Path; + Rest:= ''; + end else begin + Rest:= Copy(Path, Length(First)+2, MaxInt); + end; +end; + +function TutlMCFSection.SectionExists(Path: string): boolean; +var + f,r: String; + i: integer; +begin + SplitPath(Path, f, r); + i:= FSections.IndexOf(f); + Result:= (i >= 0) and ((r='') or (TutlMCFSection(FSections.Objects[i]).SectionExists(r))); +end; + +function TutlMCFSection.Section(Path: string): TutlMCFSection; +var + f,r: String; + i: integer; +begin + SplitPath(Path, f, r); + i:= FSections.IndexOf(f); + if r <> '' then begin + if (i >= 0) then + Result:= TutlMCFSection(FSections.Objects[i]).Section(r) + else begin + result := TutlMCFSection.Create; + fSections.AddObject(f, result); + result := result.Section(r); + end; + end else begin + if i >= 0 then + Result:= TutlMCFSection(FSections.Objects[i]) + else begin + Result:= TutlMCFSection.Create; + FSections.AddObject(f, Result); + end; + end; +end; + +procedure TutlMCFSection.DeleteSection(Name: string); +var + i: integer; +begin + i:= FSections.IndexOf(Name); + if i >= 0 then begin + FSections.Objects[i].Free; + FSections.Delete(i); + end; +end; + +function TutlMCFSection.ValueExists(Name: string): boolean; +begin + Result:= FValues.IndexOf(Name) >= 0; +end; + +function TutlMCFSection.GetInt(Name: string; Default: Int64): Int64; +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + Result:= Default + else + Result:= TutlMCFValue(FValues.Objects[i]).Value; +end; + +function TutlMCFSection.GetFloat(Name: string; Default: Double): Double; +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + Result:= Default + else + Result:= TutlMCFValue(FValues.Objects[i]).Value; +end; + +function TutlMCFSection.GetStringW(Name: string; Default: UnicodeString): UnicodeString; +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + Result:= Default + else + Result:= TutlMCFValue(FValues.Objects[i]).Value; +end; + +function TutlMCFSection.GetString(Name: string; Default: AnsiString): AnsiString; +begin + Result := AnsiString(GetStringW(Name, UnicodeString(Default))); +end; + +function TutlMCFSection.GetBool(Name: string; Default: Boolean): Boolean; +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + Result:= Default + else + Result:= TutlMCFValue(FValues.Objects[i]).Value; +end; + + +procedure TutlMCFSection.AddValueChecked(Name: String; Val: TObject); +var + i: integer; +begin + if (Length(Name) < 1) or + (Name[1] in sWhitespaceChars) or + (Name[Length(Name)] in sWhitespaceChars) then + raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]); + + for i:= 1 to Length(Name) do + if not (Name[i] in sNameValidChars) then + raise EConfigException.CreateFmt('Invalid Value Name: "%s"',[Name]); + FValues.AddObject(Name, Val); +end; + +procedure TutlMCFSection.SetInt(Name: string; Value: Int64); +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + AddValueChecked(Name, TutlMCFValue.Create(Value)) + else + TutlMCFValue(FValues.Objects[i]).Value:= Value; +end; + +procedure TutlMCFSection.SetFloat(Name: string; Value: Double); +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + AddValueChecked(Name, TutlMCFValue.Create(Value)) + else + TutlMCFValue(FValues.Objects[i]).Value:= Value; +end; + +procedure TutlMCFSection.SetString(Name: string; Value: WideString); +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + AddValueChecked(Name, TutlMCFValue.Create(Value)) + else + TutlMCFValue(FValues.Objects[i]).Value:= Value; +end; + +procedure TutlMCFSection.SetString(Name: string; Value: AnsiString); +begin + SetString(Name, WideString(Value)); +end; + +procedure TutlMCFSection.SetBool(Name: string; Value: Boolean); +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i < 0 then + AddValueChecked(Name, TutlMCFValue.Create(Value)) + else + TutlMCFValue(FValues.Objects[i]).Value:= Value; +end; + +procedure TutlMCFSection.UnsetValue(Name: string); +var + i: integer; +begin + i:= FValues.IndexOf(Name); + if i >= 0 then begin + FValues.Objects[i].Free; + FValues.Delete(i); + end; +end; + + +procedure TutlMCFSection.LoadData(Data: TStream; LineEnds: TutlMCFLineEndMarkerMode; Depth: Integer); +var + reader: TutlStreamReader; + l, sn, vn, vs: string; + se: TutlMCFSection; + va: TutlMCFValue; +begin + reader:= TutlStreamReader.Create(Data); + try + repeat + l:= reader.ReadLine; + l:= trim(l); + if (l = '') or AnsiStartsStr(sComment, l) then + continue; + if ((LineEnds in [leNone, leAcceptNoWrite]) and (l = sSectionEnd)) or + ((LineEnds in [leAcceptNoWrite, leAlways]) and (l = sSectionEnd+sLineEndMarker)) then begin + if Depth > 0 then + exit + else + raise EConfigException.Create('Encountered Section End where none was expected.'); + end; + if AnsiEndsStr(sSectionMarker, l) then begin + sn:= trim(Copy(l, 1, length(l) - length(sSectionMarker))); + if SectionExists(sn) then + raise EConfigException.Create('Redeclared Section: '+sn); + if Pos(sSectionPathDelim,sn) > 0 then + raise EConfigException.Create('Invalid Section Name: '+sn); + se:= TutlMCFSection.Create; + try + se.LoadData(Data, LineEnds, Depth + 1); + FSections.AddObject(sn, se); + except + FreeAndNil(se); + end; + end else if (Pos(sValueDelim, l) > 0) then begin + if (LineEnds in [leAcceptNoWrite, leAlways]) and AnsiEndsStr(sLineEndMarker, l) then + Delete(l, length(l), 1); + vn:= trim(Copy(l, 1, Pos(sValueDelim, l) - 1)); + vs:= trim(Copy(l, Pos(sValueDelim, l) + 1, Maxint)); + if ValueExists(vn) then + raise EConfigException.Create('Redeclared Value: '+vn); + va:= TutlMCFValue.Create(''); + try + va.LoadData(vs); + AddValueChecked(vn, va); + except + FreeAndNil(va); + end; + end else + raise EConfigException.Create('Cannot Parse Line: '+l); + until reader.IsEOF; + if Depth > 0 then + raise EConfigException.Create('Expected Section End, but reached stream end.'); + Depth:= Depth - 1; + finally + FreeAndNil(reader); + end; +end; + +procedure TutlMCFSection.SaveData(Stream: TStream; Indent: string; + LineEnds: TutlMCFLineEndMarkerMode); +var + writer: TutlStreamWriter; + i: integer; + ele, s: AnsiString; +begin + if LineEnds in [leAlways] then + ele:= sLineEndMarker + else + ele:= ''; + writer:= TutlStreamWriter.Create(Stream); + try + for i:= 0 to FValues.Count - 1 do begin + s:= Indent + FValues[i] + ' ' + sValueDelim + ' ' + TutlMCFValue(FValues.Objects[i]).SaveData + ele; + writer.WriteLine(s); + end; + + for i:= 0 to FSections.Count - 1 do begin + s:= Indent + FSections[i] + sSectionMarker; + writer.WriteLine(s); + TutlMCFSection(FSections.Objects[i]).SaveData(Stream, Indent + sIndentOnSave, LineEnds); + s:= Indent + sSectionEnd + ele; + writer.WriteLine(s); + end; + finally + FreeAndNil(writer); + end; +end; + +{ TutlMCFFile } + +constructor TutlMCFFile.Create(Data: TStream; LineEndMode: TutlMCFLineEndMarkerMode); +begin + inherited Create; + fLineEndMode:= LineEndMode; + if Assigned(Data) then + LoadFromStream(Data); +end; + +procedure TutlMCFFile.LoadFromStream(Stream: TStream); +begin + ClearSections; + ClearValues; + LoadData(Stream, fLineEndMode, 0); +end; + +procedure TutlMCFFile.SaveToStream(Stream: TStream); +begin + SaveData(Stream, '', fLineEndMode); +end; + +end. + diff --git a/uutlMcfHelper.pas b/uutlMcfHelper.pas new file mode 100644 index 0000000..5495dd2 --- /dev/null +++ b/uutlMcfHelper.pas @@ -0,0 +1,100 @@ +unit uutlMcfHelper; + +{$mode objfpc}{$H+} + +interface + +uses + ugluMatrix, ugluVector, uutlMCF, uglcLight; + +procedure utlWriteMatrix4f(const aSection: TutlMCFSection; const aMatrix: TgluMatrix4f); +function utlReadMatrix4f(const aSection: TutlMCFSection): TgluMatrix4f; +procedure utlWriteMaterial(const aSection: TutlMCFSection; const aMaterial: TglcMaterialRec); +function utlReadMaterial(const aSection: TutlMCFSection): TglcMaterialRec; +procedure utlWriteLight(const aSection: TutlMCFSection; const aLight: TglcLightRec); +function utlReadLight(const aSection: TutlMCFSection): TglcLightRec; + +implementation + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure utlWriteMatrix4f(const aSection: TutlMCFSection; const aMatrix: TgluMatrix4f); +begin + with aSection do begin + SetString('AxisX', gluVector4fToStr(aMatrix[maAxisX])); + SetString('AxisY', gluVector4fToStr(aMatrix[maAxisY])); + SetString('AxisZ', gluVector4fToStr(aMatrix[maAxisZ])); + SetString('Pos', gluVector4fToStr(aMatrix[maPos])); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlReadMatrix4f(const aSection: TutlMCFSection): TgluMatrix4f; +begin + with aSection do begin + result[maAxisX] := gluStrToVector4f(GetString('AxisX', '1; 0; 0; 0;')); + result[maAxisY] := gluStrToVector4f(GetString('AxisY', '0; 1; 0; 0;')); + result[maAxisZ] := gluStrToVector4f(GetString('AxisZ', '0; 0; 1; 0;')); + result[maPos] := gluStrToVector4f(GetString('Pos', '0; 0; 0; 1;')); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure utlWriteMaterial(const aSection: TutlMCFSection; const aMaterial: TglcMaterialRec); +begin + with aSection do begin + SetString('Ambient', gluVector4fToStr(aMaterial.Ambient)); + SetString('Diffuse', gluVector4fToStr(aMaterial.Diffuse)); + SetString('Specular', gluVector4fToStr(aMaterial.Specular)); + SetString('Emission', gluVector4fToStr(aMaterial.Emission)); + SetFloat ('Shininess', aMaterial.Shininess); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlReadMaterial(const aSection: TutlMCFSection): TglcMaterialRec; +begin + with aSection do begin + result.Ambient := gluStrToVector4f(GetString('Ambient', gluVector4fToStr(MAT_DEFAULT_AMBIENT))); + result.Diffuse := gluStrToVector4f(GetString('Diffuse', gluVector4fToStr(MAT_DEFAULT_DIFFUSE))); + result.Specular := gluStrToVector4f(GetString('Specular', gluVector4fToStr(MAT_DEFAULT_SPECULAR))); + result.Emission := gluStrToVector4f(GetString('Emission', gluVector4fToStr(MAT_DEFAULT_EMISSION))); + result.Shininess := GetFloat('Shininess', MAT_DEFAULT_SHININESS); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure utlWriteLight(const aSection: TutlMCFSection; const aLight: TglcLightRec); +begin + with aSection do begin + SetString('Ambient', gluVector4fToStr(aLight.Ambient)); + SetString('Diffuse', gluVector4fToStr(aLight.Diffuse)); + SetString('Specular', gluVector4fToStr(aLight.Specular)); + SetString('Position', gluVector4fToStr(aLight.Position)); + SetString('SpotDirection', gluVector3fToStr(aLight.SpotDirection)); + SetFloat ('SpotExponent', aLight.SpotExponent); + SetFloat ('SpotCutoff', aLight.SpotCutoff); + SetFloat ('ConstantAtt', aLight.ConstantAtt); + SetFloat ('LinearAtt', aLight.LinearAtt); + SetFloat ('QuadraticAtt', aLight.QuadraticAtt); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlReadLight(const aSection: TutlMCFSection): TglcLightRec; +begin + with aSection do begin + result.Ambient := gluStrToVector4f(GetString('Ambient', gluVector4fToStr(LIGHT_DEFAULT_AMBIENT))); + result.Diffuse := gluStrToVector4f(GetString('Diffuse', gluVector4fToStr(LIGHT_DEFAULT_DIFFUSE))); + result.Specular := gluStrToVector4f(GetString('Specular', gluVector4fToStr(LIGHT_DEFAULT_SPECULAR))); + result.Position := gluStrToVector4f(GetString('Position', gluVector4fToStr(LIGHT_DEFAULT_POSITION))); + result.SpotDirection := gluStrToVector3f(GetString('SpotDirection', gluVector3fToStr(LIGHT_DEFAULT_SPOT_DIRECTION))); + result.SpotExponent := GetFloat ('SpotExponent', LIGHT_DEFAULT_SPOT_EXPONENT); + result.SpotCutoff := GetFloat ('SpotCutoff', LIGHT_DEFAULT_SPOT_CUTOFF); + result.ConstantAtt := GetFloat ('ConstantAtt', LIGHT_DEFAULT_CONSTANT_ATT); + result.LinearAtt := GetFloat ('LinearAtt', LIGHT_DEFAULT_LINEAR_ATT); + result.QuadraticAtt := GetFloat ('QuadraticAtt', LIGHT_DEFAULT_QUADRATIC_ATT); + end; +end; + +end. + diff --git a/uutlMessageThread.pas b/uutlMessageThread.pas new file mode 100644 index 0000000..8ff7d10 --- /dev/null +++ b/uutlMessageThread.pas @@ -0,0 +1,453 @@ +unit uutlMessageThread; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit definiert einen Thread, der mit Hilfe von Messages Daten synchronisiert + mit anderen Threads austauschen kann } + +{$mode objfpc}{$H+} +{$DEFINE USE_SPINLOCK} + +interface + +uses + Classes, SysUtils, syncobjs, uutlMessages; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlMessageThread = class(TThread, IUnknown) + protected type + TSingleLinkedListItem = class + msg: TutlMessage; + next: TSingleLinkedListItem; + end; + private + {$IFDEF USE_SPINLOCK} + fLocked: Cardinal; + {$ELSE} + fCritSec: TCriticalSection; + {$ENDIF} + fMsgEvent: TEvent; + procedure PushMsg(aMessage: TutlMessage); + function PullMsg: TutlMessage; + procedure ClearMessages; + protected + fRefCount : longint; + { implement methods of IUnknown } + function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} iid : tguid;out obj) : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; + function _AddRef : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; + function _Release : longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; virtual; + protected + fFirst: TSingleLinkedListItem; + fLast: TSingleLinkedListItem; + + procedure LockMessages; + procedure UnlockMessages; + function WaitForMessages(const aWaitTime: Cardinal = INFINITE): Boolean; + function ProcessMessages: Boolean; virtual; + procedure ProcessMessage(const {%H-}aMessage: TutlMessage); virtual; + public + //Messages Objects passed to PostMessage will be freed automatically + procedure PostMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt); overload; + procedure PostMessage(const aID: Cardinal; const aArgs: TObject); overload; + procedure PostMessage(const aMsg: TutlMessage); overload; + + //Messages Objects passed to SendMessage must be freed by user when WaitResult is wrSignaled (otherwise the thread will handle it) + function SendMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt; + const aWaitTime: Cardinal = INFINITE): TWaitResult; overload; + function SendMessage(const aID: Cardinal; const aArgs: TObject; + const aWaitTime: Cardinal = INFINITE): TWaitResult; overload; + function SendMessage(const aMsg: TutlSynchronousMessage; + const aWaitTime: Cardinal = INFINITE): TWaitResult; + + constructor Create(CreateSuspended: Boolean; const StackSize: SizeUInt=DefaultStackSize); + destructor Destroy; override; + end; + + //Messages Objects passed to PostMessage will be freed automatically + function utlPostMessage(const aThreadID: TThreadID; const aID: Cardinal; const aWParam, aLParam: PtrInt): Boolean; overload; + function utlPostMessage(const aThreadID: TThreadID; const aID: Cardinal; const aArgs: TObject): Boolean; overload; + function utlPostMessage(const aThreadID: TThreadID; const aMsg: TutlMessage): Boolean; overload; + + //Messages Objects passed to SendMessage must be freed by user when WaitResult is wrSignaled (otherwise the thread will handle it) + function utlSendMessage(const aThreadID: TThreadID; const aID: Cardinal; const aWParam, aLParam: PtrInt; + const aWaitTime: Cardinal = INFINITE): TWaitResult; overload; + function utlSendMessage(const aThreadID: TThreadID; const aID: Cardinal; const aArgs: TObject; + const aWaitTime: Cardinal = INFINITE): TWaitResult; overload; + function utlSendMessage(const aThreadID: TThreadID; const aMsg: TutlSynchronousMessage; + const aWaitTime: Cardinal = INFINITE): TWaitResult; overload; + +implementation + +uses + uutlLogger, uutlGenerics, uutlExceptions; + +type + TutlMessageThreadMap = class(specialize TutlMap<TThreadID, TutlMessageThread>) + private + fCS: TCriticalSection; + public + procedure Lock; + procedure Release; + constructor Create(const aOwnsObjects: Boolean = true); + destructor Destroy; override; + end; + +var + Threads: TutlMessageThreadMap; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlPostMessage(const aThreadID: TThreadID; const aID: Cardinal; const aWParam, aLParam: PtrInt): Boolean; +begin + result := utlPostMessage(aThreadID, TutlMessage.Create(aID, aWParam, aLParam)); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlPostMessage(const aThreadID: TThreadID; const aID: Cardinal; const aArgs: TObject): Boolean; +begin + result := utlPostMessage(aThreadID, TutlMessage.Create(aID, aArgs)); +end; + +function utlPostMessage(const aThreadID: TThreadID; const aMsg: TutlMessage): Boolean; +var + t: TutlMessageThread; +begin + Threads.Lock; + try + t := Threads[aThreadID]; + finally + Threads.Release; + end; + result := Assigned(t); + if (result) then + t.PostMessage(aMsg) + else + aMsg.Free; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlSendMessage(const aThreadID: TThreadID; const aID: Cardinal; const aWParam, aLParam: PtrInt; const aWaitTime: Cardinal): TWaitResult; +begin + result := utlSendMessage(aThreadID, TutlSynchronousMessage.Create(aID, aWParam, aLParam), aWaitTime); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlSendMessage(const aThreadID: TThreadID; const aID: Cardinal; const aArgs: TObject; const aWaitTime: Cardinal): TWaitResult; +begin + result := utlSendMessage(aThreadID, TutlSynchronousMessage.Create(aID, aArgs), aWaitTime); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function utlSendMessage(const aThreadID: TThreadID; const aMsg: TutlSynchronousMessage; const aWaitTime: Cardinal): TWaitResult; +var + t: TutlMessageThread; +begin + Threads.Lock; + try + t := Threads[aThreadID]; + finally + Threads.Release; + end; + if Assigned(t) then + result := t.SendMessage(aMsg) + else begin + result := wrError; + aMsg.Free; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMessageThreadMap////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThreadMap.Lock; +begin + fCS.Acquire; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThreadMap.Release; +begin + fCS.Release; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMessageThreadMap.Create(const aOwnsObjects: Boolean); +begin + inherited; + fCS:= TCriticalSection.Create; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMessageThreadMap.Destroy; +begin + fCS.Acquire; + try + inherited Destroy; + finally + fCS.Release; + end; + FreeAndNil(fCS); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMessageThread///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.PushMsg(aMessage: TutlMessage); +begin + LockMessages; + try + if not Assigned(fLast) then + exit; + fLast.next := TSingleLinkedListItem.Create; + fLast.next.msg := aMessage; + fLast := fLast.next; + fMsgEvent.SetEvent; + finally + UnlockMessages; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.PullMsg: TutlMessage; +var + old: TSingleLinkedListItem; +begin + result := nil; + LockMessages; + try + if Assigned(fFirst) and Assigned(fFirst.next) then begin + old := fFirst; + fFirst := old.next; + result := fFirst.msg; + old.Free; + if not Assigned(fFirst.next) then + fMsgEvent.ResetEvent; + end; + finally + UnlockMessages; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.ClearMessages; +var + m: TutlMessage; +begin + repeat + m := PullMsg; + if Assigned(m) then + m.Free; + until not Assigned(m); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.QueryInterface(constref iid: tguid; out obj): longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + if getinterface(iid,obj) then + result := S_OK + else + result := longint(E_NOINTERFACE); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread._AddRef: longint;{$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + result := InterLockedIncrement(fRefCount); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread._Release: longint; {$IFNDEF WINDOWS}cdecl{$ELSE}stdcall{$ENDIF}; +begin + result := InterLockedDecrement(fRefCount); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.LockMessages; +{$IFDEF USE_SPINLOCK} +var + lock: Cardinal; +begin + repeat + lock := InterLockedExchange(fLocked, 1); + until (lock = 0); +{$ELSE} +begin + fCritSec.Enter; +{$ENDIF} +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.UnlockMessages; +begin + {$IFDEF USE_SPINLOCK} + InterLockedExchange(fLocked, 0); + {$ELSE} + fCritSec.Leave; + {$ENDIF} +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.WaitForMessages(const aWaitTime: Cardinal): Boolean; +var + wr: TWaitResult; +begin + wr := fMsgEvent.WaitFor(aWaitTime); + result := (wr = wrSignaled); + if not result and (wr <> wrTimeout) then + raise EWait.Create('Error while waiting for messages', wr); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.ProcessMessages: Boolean; +var + m: TutlMessage; + empty: Boolean; +begin + empty := false; + result := false; + repeat + try + m := PullMsg; //nur beim holen einer Message Locken sonst evtl. DeadLock + if Assigned(m) then begin + result := true; + try + ProcessMessage(m); + finally + if (m is TutlSynchronousMessage) then + (m as TutlSynchronousMessage).Finish + else + FreeAndNil(m); + end; + end else + empty := true; + except + on e: Exception do begin + utlLogger.Error(self, 'error while progressing message %s(ID: %d; wParam: %s; lParam: %s): %s - %s', [ + m.ClassName, + m.ID, + IntToHex(m.wParam, SizeOf(m.wParam) div 4), + IntToHex(m.wParam, SizeOf(m.wParam) div 4), + e.ClassName, + e.Message]); + end; + end; + until empty; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.ProcessMessage(const aMessage: TutlMessage); +begin + case aMessage.ID of + MSG_CALLBACK: + (aMessage as TutlCallbackMsg).ExecuteCallback; + MSG_SYNC_CALLBACK: + (aMessage as TutlSyncCallbackMsg).ExecuteCallback; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.PostMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt); +var + m: TutlMessage; +begin + m := TutlMessage.Create(aID, aWParam, aLParam); + PushMsg(m); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.PostMessage(const aID: Cardinal; const aArgs: TObject); +var + m: TutlMessage; +begin + m := TutlMessage.Create(aID, aArgs); + PushMsg(m); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlMessageThread.PostMessage(const aMsg: TutlMessage); +begin + PushMsg(aMsg); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.SendMessage(const aID: Cardinal; const aWParam, aLParam: PtrInt; const aWaitTime: Cardinal): TWaitResult; +var + m: TutlSynchronousMessage; +begin + m := TutlSynchronousMessage.Create(aID, aWParam, aLParam); + result := SendMessage(m, aWaitTime); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.SendMessage(const aID: Cardinal; const aArgs: TObject; const aWaitTime: Cardinal): TWaitResult; +var + m: TutlSynchronousMessage; +begin + m := TutlSynchronousMessage.Create(aID, aArgs); + result := SendMessage(m, aWaitTime); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlMessageThread.SendMessage(const aMsg: TutlSynchronousMessage; const aWaitTime: Cardinal): TWaitResult; +begin + PushMsg(aMsg); + result := aMsg.WaitFor(aWaitTime); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMessageThread.Create(CreateSuspended: Boolean; const StackSize: SizeUInt); +begin + inherited Create(CreateSuspended, StackSize); + fMsgEvent := TEvent.Create(nil, true, false, ''); + fFirst := TSingleLinkedListItem.Create; + fLast := fFirst; + Threads.Lock; + try + Threads.Add(ThreadID, self); + finally + Threads.Release; + end; + {$IFNDEF USE_SPINLOCK} + fCritSec := TCriticalSection.Create; + {$ENDIF} +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMessageThread.Destroy; +begin + Threads.Lock; + try + Threads.Delete(ThreadID); + finally + Threads.Release; + end; + ClearMessages; + FreeAndNil(fFirst); + fLast := nil; + {$IFNDEF USE_SPINLOCK} + FreeAndNil(fCritSec); + {$ENDIF} + FreeAndNil(fMsgEvent); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +initialization + Threads := TutlMessageThreadMap.Create(false); + +finalization + Threads.Lock; + try + while (Threads.Count > 0) do + Threads.ValueAt[Threads.Count-1].Free; + finally + Threads.Release; + end; + FreeAndNil(Threads); + +end. + diff --git a/uutlMessages.pas b/uutlMessages.pas new file mode 100644 index 0000000..613e500 --- /dev/null +++ b/uutlMessages.pas @@ -0,0 +1,201 @@ +unit uutlMessages; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält verschiedene Klassen, die Messages definieren, + die zwischen utlMessageThreads ausgetauscht werden können } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, syncobjs; + +const + //General + MSG_CALLBACK = $00010001; //TutlCallbackMsg + + MSG_SYNC_CALLBACK = $00010002; //TutlSyncCallbackMsg + + //User + MSG_USER = $F0000000; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlMessage = class(TObject) + private + fID: Cardinal; + fWParam: PtrInt; + fLParam: PtrInt; + fArgs: TObject; + fOwnsObjects: Boolean; + public + property ID: Cardinal read fID; + property WParam: PtrInt read fWParam; + property LParam: PtrInt read fLParam; + property Args: TObject read fArgs; + property OwnsObjects: Boolean read fOwnsObjects write fOwnsObjects; + + constructor Create(const aID: Cardinal; const aWParam, aLParam: PtrInt); overload; + constructor Create(const aID: Cardinal; const aArgs: TObject; const aOwnsObjects: Boolean = true); overload; + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlSynchronousMessage = class(TutlMessage) + private + fEvent: TEvent; + fFreeOnFinish: Boolean; + fLock: Integer; + procedure Lock; + procedure Unlock; + public + procedure Finish; + function WaitFor(const aTimeout: Cardinal): TWaitResult; + constructor Create(const aID: Cardinal; const aWParam, aLParam: PtrInt); overload; + constructor Create(const {%H-}aID: Cardinal; const aArgs: TObject; const aOwnsObjects: Boolean = true); overload; + destructor Destroy; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlCallbackMsg = class(TutlMessage) + public + procedure ExecuteCallback; virtual; + constructor Create; overload; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlSyncCallbackMsg = class(TutlSynchronousMessage) + public + procedure ExecuteCallback; virtual; + constructor Create; overload; + end; + +implementation + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlMessage/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMessage.Create(const aID: Cardinal; const aWParam, aLParam: PtrInt); +begin + inherited Create; + fID := aID; + fWParam := aWParam; + fLParam := aLParam; + fArgs := nil; + fOwnsObjects := true; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlMessage.Create(const aID: Cardinal; const aArgs: TObject; const aOwnsObjects: Boolean); +begin + inherited Create; + fID := aID; + fWParam := 0; + fLParam := 0; + fArgs := aArgs; + fOwnsObjects := aOwnsObjects; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlMessage.Destroy; +begin + if Assigned(fArgs) and fOwnsObjects then + FreeAndNil(fArgs); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlSynchronousMessage////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSynchronousMessage.Lock; +begin + repeat until (InterLockedExchange(fLock, 1) = 0); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSynchronousMessage.Unlock; +begin + InterLockedExchange(fLock, 0); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSynchronousMessage.Finish; +begin + fEvent.SetEvent; + Lock; + if fFreeOnFinish then + Free + else + Unlock; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSynchronousMessage.WaitFor(const aTimeout: Cardinal): TWaitResult; +begin + Lock; + try + result := fEvent.WaitFor(aTimeout); + fFreeOnFinish := (result <> wrSignaled); + finally + Unlock; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlSynchronousMessage.Create(const aID: Cardinal; const aWParam, aLParam: PtrInt); +begin + inherited Create(aID, aWParam, aLParam); + fEvent := TEvent.Create(nil, true, false, ''); + fFreeOnFinish := false; + fLock := 0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlSynchronousMessage.Create(const aID: Cardinal; const aArgs: TObject; const aOwnsObjects: Boolean); +begin + inherited Create(ID, aArgs, aOwnsObjects); + fEvent := TEvent.Create(nil, true, false, ''); + fFreeOnFinish := false; + fLock := 0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlSynchronousMessage.Destroy; +begin + fEvent.SetEvent; + FreeAndNil(fEvent); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlCallbackMsg/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlCallbackMsg.ExecuteCallback; +begin + //DUMMY +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlCallbackMsg.Create; +begin + inherited Create(MSG_CALLBACK, 0, 0); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlSyncCallbackMsg/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSyncCallbackMsg.ExecuteCallback; +begin + //DUMMY +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlSyncCallbackMsg.Create; +begin + inherited Create(MSG_SYNC_CALLBACK, 0, 0); +end; + +end. + diff --git a/uutlPlatform.pas b/uutlPlatform.pas new file mode 100644 index 0000000..2426798 --- /dev/null +++ b/uutlPlatform.pas @@ -0,0 +1,93 @@ +unit uutlPlatform; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit implementiert Methoden mit denen ein String generiert werden kann, + welcher das System auf dem die Anwendung läuft identifiziert } + + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +function GetPlatformIdentitfier: string; + +implementation + +uses + {$ifdef WINDOWS} + Windows + {$endif} + ; + +{$ifdef WINDOWS} +function GetWindowsVersionStr(const aDefault: String): string; +var + osv: TOSVERSIONINFO; + ver: cardinal; +begin + Result:= aDefault; + osv.dwOSVersionInfoSize:= SizeOf(osv); + if GetVersionEx(osv) then begin + ver:= MAKELONG(osv.dwMinorVersion, osv.dwMajorVersion); + // positive overflow: if system is newer, always detect as newest we knew instead of failing + if ver >= $00060003 then + Result:= '8_1' + else + if ver >= $00060002 then + Result:= '8' + else + if ver >= $00060001 then + Result:= '7' + else + if ver >= $00060000 then + Result:= 'Vista' + else + if ver >= $00050002 then + Result:= '2003' + else + if ver >= $00050001 then + Result:= 'XP' + else + if ver >= $00050000 then + Result:= '2000' + else + if ver >= $00040000 then + Result:= 'NT4'; + // ignore NT3, hmkay?; + end; +end; +{$endif} + +function GetPlatformIdentitfier: string; +var + os,ver,arch: string; +begin + Result:= ''; + os:= ''; + ver:= 'generic'; + arch:= ''; + {$if defined(WINDOWS)} + os:= 'mswin'; + ver:= GetWindowsVersionStr(ver); + {$elseif defined(LINUX)} + os:= 'linux'; + {$Warning System Version String missing!} + {$endif} + + {$if defined(CPUX86)} + arch:= 'x86'; + {$elseif defined(cpux86_64)} + arch:= 'x64'; + {$else} + {$Error Unknown Architecture!} + {$endif} + Result:= format('%s-%s-%s', [os, ver, arch]); +end; + + +end. + diff --git a/uutlProfilerBinary.inc b/uutlProfilerBinary.inc new file mode 100644 index 0000000..83c4d55 --- /dev/null +++ b/uutlProfilerBinary.inc @@ -0,0 +1,63 @@ +{$ERROR Do not use, untested/WIP/useless!} +{$ifdef __HEAD} +TProfileBinary = class(TProfileDataFile) +private type + TEnterRec = packed record + Thread: TThreadID; + When: Int64; + Line: Integer; + Func, Src: ShortString; + end; + TLeaveRec = packed record + Thread: TThreadID; + When: Int64; + end; +private + fDF: TMemoryStream; +public + constructor Create(const aFileName: string); + destructor Destroy; override; + procedure WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); override; + procedure WriteLeave(Thread: TThreadID; When: Int64); override; +end; + +{$ELSE} + +{ TProfileBinary } + +constructor TProfileBinary.Create(const aFileName: string); +begin + inherited; + fDF:= TMemoryStream.Create; + fDF.SetSize(50000000); +end; + +destructor TProfileBinary.Destroy; +begin + FreeAndNil(fDF); + inherited; +end; + +procedure TProfileBinary.WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); +var + t: TEnterRec; +begin + t.When:= When; + t.Thread:= Thread; + t.Func:= Func; + t.Src:= Src; + t.Line:= Line; + fDF.Write(t, sizeof(t)); +end; + +procedure TProfileBinary.WriteLeave(Thread: TThreadID; When: Int64); +var + t: TLeaveRec; +begin + t.When:= When; + t.Thread:= Thread; + fDF.Write(t, sizeof(t)); +end; +{$ENDIF} + + diff --git a/uutlProfilerPlainText.inc b/uutlProfilerPlainText.inc new file mode 100644 index 0000000..a5f74ac --- /dev/null +++ b/uutlProfilerPlainText.inc @@ -0,0 +1,49 @@ +{$ifdef __HEAD} +TProfilePlainText = class(TProfileDataFile) +private + fDF: Textfile; + fBuffer: array[0..16*1024-1] of byte; +public + constructor Create(const aFileName: string); + destructor Destroy; override; + procedure WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); override; + procedure WriteLeave(Thread: TThreadID; When: Int64); override; +end; + +{$ELSE} + +{ TProfilePlainText } + +constructor TProfilePlainText.Create(const aFileName: string); +begin + inherited; + AssignFile(fDF, aFileName); + SetTextBuf(fDF, {%H-}fBuffer[0], sizeof(fBuffer)); + Rewrite(fDF); +end; + +destructor TProfilePlainText.Destroy; +begin + CloseFile(fDF); + inherited; +end; + +procedure TProfilePlainText.WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); +var + l: string; +begin + l:= hexStr(When, 16)+ ';'+hexStr(Thread, 4)+ ';'+Src+ ';'+IntToStr(Line)+';'+Func; + WriteLn(fDF, l); +end; + +procedure TProfilePlainText.WriteLeave(Thread: TThreadID; When: Int64); +var + l: string; +begin + l:= hexStr(When, 16)+ ';'+hexStr(Thread, 4)+ ';'; + WriteLn(fDF, l); +end; + +{$ENDIF} + + diff --git a/uutlProfilerPlainTextMMap.inc b/uutlProfilerPlainTextMMap.inc new file mode 100644 index 0000000..1b6a4ef --- /dev/null +++ b/uutlProfilerPlainTextMMap.inc @@ -0,0 +1,46 @@ +{$ifdef __HEAD} +TProfilePlainTextMMap = class(TProfileDataFile) +private + fDF: TFastFileStream; +public + constructor Create(const aFileName: string); + destructor Destroy; override; + procedure WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); override; + procedure WriteLeave(Thread: TThreadID; When: Int64); override; +end; + +{$ELSE} + +{ TProfilePlainTextMMap } + +constructor TProfilePlainTextMMap.Create(const aFileName: string); +begin + inherited; + fDF:= TFastFileStream.Create(aFileName, fmCreate, fmShareExclusive); +end; + +destructor TProfilePlainTextMMap.Destroy; +begin + FreeAndNil(fDF); + inherited; +end; + +procedure TProfilePlainTextMMap.WriteEnter(Thread: TThreadID; When: Int64; Func, Src: String; Line: Integer); +var + l: string; +begin + l:= hexStr(When, 16)+ ';'+hexStr(Thread, 4)+ ';'+Src+ ';'+IntToStr(Line)+';'+Func + #13#10; + fDF.Write(l[1], Length(l)); +end; + +procedure TProfilePlainTextMMap.WriteLeave(Thread: TThreadID; When: Int64); +var + l: string; +begin + l:= hexStr(When, 16)+ ';'+hexStr(Thread, 4)+ ';'#13#10; + fDF.Write(l[1], Length(l)); +end; + +{$ENDIF} + + diff --git a/uutlSetHelper.inc b/uutlSetHelper.inc new file mode 100644 index 0000000..8e76f86 --- /dev/null +++ b/uutlSetHelper.inc @@ -0,0 +1,71 @@ +{$IF defined(__SET_INTERFACE)} + +type __SET_HELPER = class +public + class function {%H}ToString(const Value: __SET_TYPE): String; reintroduce; + class function TryToSet(const Str: String; out Value: __SET_TYPE): boolean; overload; + class function ToSet(const Str: String; const aDefault: __SET_TYPE): __SET_TYPE; overload; + class function ToSet(const Str: String): __SET_TYPE; overload; +end; +{$ELSEIF defined (__SET_IMPLEMENTATION)} + +class function __SET_HELPER.ToString(const Value: __SET_TYPE): String; +var + m: __ENUM_TYPE; +begin + Result:= ''; + for m in __ENUM_HELPER.Values do + if m in Value then begin + if Result > '' then + Result:= Result + ', '; + Result:= Result + __ENUM_HELPER.ToString(m); + end; +end; + +class function __SET_HELPER.ToSet(const Str: String): __SET_TYPE; +begin + if not TryToSet(Str, Result) then + raise SysUtils.EConvertError.CreateFmt('"%s" is an invalid value',[Str]); +end; + +class function __SET_HELPER.ToSet(const Str: String; const aDefault: __SET_TYPE): __SET_TYPE; +begin + if not TryToSet(Str, Result) then + Result:= aDefault; +end; + +class function __SET_HELPER.TryToSet(const Str: String; out Value: __SET_TYPE): boolean; +var + i, j: Integer; + s: String; + m: __ENUM_TYPE; +begin + Result:= true; + Value := []; + i := 1; + j := 1; + while (i <= Length(Str)) do begin + if (Str[i] = ',') then begin + s := Trim(copy(Str, j, i-j)); + Result:= Result and __ENUM_HELPER.TryToEnum(s, m); + if not Result then + Exit; + Include(Value, m); + j := i+1; + end; + inc(i); + end; + s := Trim(copy(Str, j, i-j)); + if (s <> '') then begin + Result:= Result and __ENUM_HELPER.TryToEnum(s, m); + if not Result then + Exit; + Include(Value, m); + end; +end; + +{$ENDIF} +{$undef __SET_HELPER} +{$undef __SET_TYPE} +{$undef __ENUM_TYPE} +{$undef __ENUM_HELPER} diff --git a/uutlSettings.pas b/uutlSettings.pas new file mode 100644 index 0000000..2fddde7 --- /dev/null +++ b/uutlSettings.pas @@ -0,0 +1,371 @@ +unit uutlSettings; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit stellt ein Framework zur Verfügung mit dessen Hilfe Einstellungs-Blöcke + in ein MCF File geladen und geschreieben werden können } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + uutlMCF, uutlGenerics, uutlMessageThread; + +type + TutlSettingsBlock = class + constructor Create; virtual; + + procedure LoadDefaults; virtual; abstract; + procedure LoadFromConfig(const aMcf: TutlMCFSection); virtual; abstract; + procedure SaveToConfig(const aMcf: TutlMCFSection); virtual; abstract; + end; + TutlSettingsBlockClass = class of TutlSettingsBlock; + + TutlSettingsUpdateOp = (opInstanceChanged, opDataChanged); + TutlSettingsUpdateEvent = procedure (const aUpdateOp: TutlSettingsUpdateOp; const aOld, aNew: TutlSettingsBlock) of object; + TutlSettingsUpdateEventCntr = packed record + Callback: TutlSettingsUpdateEvent; + ThreadID: TThreadID; + end; + TutlSettingsUpdateEventCntrEqComp = class(TInterfacedObject, specialize IutlEqualityComparer<TutlSettingsUpdateEventCntr>) + public + function EqualityCompare(const i1, i2: TutlSettingsUpdateEventCntr): Boolean; + end; + + TutlSettings = class + private type + TutlSettingsUpdateEventList = specialize TutlCustomList<TutlSettingsUpdateEventCntr>; + TBlockData = class + Instance, OldInstance: TutlSettingsBlock; + Events: TutlSettingsUpdateEventList; + procedure CallEvents(const aOp: TutlSettingsUpdateOp; const aOld, aNew: TutlSettingsBlock); + constructor Create; + destructor Destroy; override; + end; + + TBlockList = specialize TutlMap<String, TBlockData>; + private + fBlocks: TBlockList; + fRaiseChangedEventOnLoad: Boolean; + procedure CopyInstance(O, N: TutlSettingsBlock); + public + property RaiseChangedEventOnLoad: Boolean read fRaiseChangedEventOnLoad write fRaiseChangedEventOnLoad; + + function RegisterBlock(const aName: String; const aClass: TutlSettingsBlockClass; const aOnUpdateEvent: TutlSettingsUpdateEvent): TutlSettingsBlock; + procedure UnregisterBlockCallback(const aName: String; const aOnUpdateEvent: TutlSettingsUpdateEvent); + procedure UnregisterBlockCallbacks(const aObj: TObject); + + function Block(const aName: String; out aBlock): boolean; + procedure Changed(const aBlock: TutlSettingsBlock); + + procedure LoadFromConfig(const aMcf: TutlMCFSection); + procedure SaveToConfig(const aMcf: TutlMCFSection); + + procedure LoadFromFile(const aFile: string); + procedure SaveToFile(const aFile: string); + + constructor Create; + destructor Destroy; override; + end; + +operator = (const i1, i2: TutlSettingsUpdateEventCntr): Boolean; inline; + +var + utlSettings: TutlSettings; + +implementation + +uses + uutlExceptions, Forms, uvfsManager, uutlMessages, syncobjs; + +const + SETTINGS_MSG_WAIT_TIME = 1000; //ms + +type + TSettingsBlockChangedMsg = class(TutlSyncCallbackMsg) + private + fCallback: TutlSettingsUpdateEvent; + fOperation: TutlSettingsUpdateOp; + fOld: TutlSettingsBlock; + fNew: TutlSettingsBlock; + public + procedure ExecuteCallback; override; + constructor Create(const aCallback: TutlSettingsUpdateEvent; const aOp: TutlSettingsUpdateOp; + const aOld, aNew: TutlSettingsBlock); + end; + +operator = (const i1, i2: TutlSettingsUpdateEventCntr): Boolean; +begin + result := + (i1.Callback = i2.Callback) and + (i2.ThreadID = i2.ThreadID); +end; + +function TutlSettingsUpdateEventCntrEqComp.EqualityCompare(const i1, i2: TutlSettingsUpdateEventCntr): Boolean; +begin + result := (i1 = i2); +end; + +{ TSettingsBlockChangedMsg } + +procedure TSettingsBlockChangedMsg.ExecuteCallback; +begin + fCallback(fOperation, fOld, fNew); +end; + +constructor TSettingsBlockChangedMsg.Create(const aCallback: TutlSettingsUpdateEvent; + const aOp: TutlSettingsUpdateOp; const aOld, aNew: TutlSettingsBlock); +begin + inherited Create; + fCallback := aCallback; + fOperation := aOp; + fOld := aOld; + fNew := aNew; +end; + +{ TutlSettings.TBlockData } + +procedure TutlSettings.TBlockData.CallEvents(const aOp: TutlSettingsUpdateOp; + const aOld, aNew: TutlSettingsBlock); +var + current: TThreadID; + cntr: TutlSettingsUpdateEventCntr; + msg: TSettingsBlockChangedMsg; +begin + current := GetCurrentThreadId; + for cntr in Events do begin + if (cntr.ThreadID <> current) then begin + msg := TSettingsBlockChangedMsg.Create(cntr.Callback, aOp, aOld, aNew); + if utlSendMessage(cntr.ThreadID, msg, SETTINGS_MSG_WAIT_TIME) = wrSignaled then + msg.Free; + end else + cntr.Callback(aOp, aOld, aNew); + end; +end; + +constructor TutlSettings.TBlockData.Create; +begin + inherited; + Events:= TutlSettingsUpdateEventList.Create(TutlSettingsUpdateEventCntrEqComp.Create); +end; + +destructor TutlSettings.TBlockData.Destroy; +begin + FreeAndNil(Events); + FreeAndNil(Instance); + FreeAndNil(OldInstance); + inherited Destroy; +end; + +{ TutlSettingsBlock } + +constructor TutlSettingsBlock.Create; +begin + inherited; + LoadDefaults; +end; + +{ TutlSettings } + +function TutlSettings.RegisterBlock(const aName: String; const aClass: TutlSettingsBlockClass; + const aOnUpdateEvent: TutlSettingsUpdateEvent): TutlSettingsBlock; +var + i: integer; + bd: TBlockData; + cntr: TutlSettingsUpdateEventCntr; +begin + Result:= nil; + + if aName = '' then + raise EInvalidOperation.Create('Empty Settings section name.'); + + i:= fBlocks.IndexOf(aName); + if i>=0 then begin + bd:= fBlocks.ValueAt[i]; + // gleicher name, instance ist gleiche oder spezifischere klasse + if bd.Instance is aClass then begin + if Assigned(aOnUpdateEvent) then begin + cntr.Callback := aOnUpdateEvent; + cntr.ThreadID := GetCurrentThreadId; + bd.Events.Add(cntr); + end; + Exit(bd.Instance) + end else + // gleicher name, neue klasse ist spezifischer + if aClass.InheritsFrom(bd.Instance.ClassType) then begin + Result:= aClass.Create; + CopyInstance(bd.Instance, Result); + bd.CallEvents(opInstanceChanged, bd.Instance, Result); + bd.Instance.Free; + bd.OldInstance.Free; + bd.Instance:= aClass.Create; + bd.OldInstance:= aClass.Create; + if Assigned(aOnUpdateEvent) then begin + cntr.Callback := aOnUpdateEvent; + cntr.ThreadID := GetCurrentThreadId; + bd.Events.Add(cntr); + end; + Exit; + end + // gleicher name, aber komplett andere klasse + else + raise EInvalidOperation.CreateFmt('Duplicate Settings entry: %s', [aName]); + end; + + for bd in fBlocks do + // verwandte klasse aber anderer name (wäre es der gleiche wäre das schon oben abgefangen) + if (bd.Instance is aClass) or (aClass.InheritsFrom(bd.Instance.ClassType)) then + raise EInvalidOperation.CreateFmt('Reused Settings class: %s', [aClass.ClassName]); + + // neuer name, neue klasse + bd:= TBlockData.Create; + bd.Instance:= aClass.Create; + bd.OldInstance:= aClass.Create; + if Assigned(aOnUpdateEvent) then begin + cntr.Callback := aOnUpdateEvent; + cntr.ThreadID := GetCurrentThreadId; + bd.Events.Add(cntr); + end; + + fBlocks.Add(aName, bd); + Result:= bd.Instance; +end; + +procedure TutlSettings.UnregisterBlockCallback(const aName: String; const aOnUpdateEvent: TutlSettingsUpdateEvent); +var + i: integer; + bd: TBlockData; +begin + i:= fBlocks.IndexOf(aName); + if i >= 0 then begin + bd := fBlocks.ValueAt[i]; + for i := bd.Events.Count-1 downto 0 do + if (bd.Events.Items[i].Callback = aOnUpdateEvent) then + bd.Events.Delete(i); + end; +end; + +procedure TutlSettings.UnregisterBlockCallbacks(const aObj: TObject); +var + bd: TBlockData; + i: integer; +begin + for bd in fBlocks do + for i:= bd.Events.Count-1 downto 0 do + if TMethod(bd.Events[i].Callback).Data = Pointer(aObj) then + bd.Events.Delete(i); +end; + +procedure TutlSettings.CopyInstance(O, N: TutlSettingsBlock); +var + tmp: TutlMCFSection; +begin + tmp:= TutlMCFSection.Create; + try + O.SaveToConfig(tmp); + N.LoadFromConfig(tmp); + finally + FreeAndNil(tmp); + end; +end; + +function TutlSettings.Block(const aName: String; out aBlock): boolean; +var + i: integer; + bd: TBlockData; +begin + i := fBlocks.IndexOf(aName); + Result := (i >= 0); + if Result then begin + bd:= fBlocks.ValueAt[i]; + CopyInstance(bd.Instance, bd.OldInstance); + TutlSettingsBlock(aBlock):= bd.Instance; + end; +end; + +procedure TutlSettings.Changed(const aBlock: TutlSettingsBlock); +var + bd: TBlockData; +begin + for bd in fBlocks do + if bd.Instance = aBlock then begin + bd.CallEvents(opDataChanged, bd.OldInstance, bd.Instance); + exit; + end; +end; + +procedure TutlSettings.LoadFromConfig(const aMcf: TutlMCFSection); +var + i: Integer; + b: TBlockData; +begin + for i := 0 to fBlocks.Count-1 do begin + b := fBlocks.ValueAt[i]; + b.Instance.LoadFromConfig(aMcf.Section(fBlocks.Keys[i])); + if fRaiseChangedEventOnLoad then + Changed(b.Instance); + end; +end; + +procedure TutlSettings.SaveToConfig(const aMcf: TutlMCFSection); +var + i: integer; +begin + for i:= 0 to fBlocks.Count-1 do + fBlocks.ValueAt[i].Instance.SaveToConfig(aMcf.Section(fBlocks.Keys[i])); +end; + +procedure TutlSettings.LoadFromFile(const aFile: string); +var + sh: IStreamHandle; + mcf: TutlMCFFile; +begin + if vfsManager.ReadFile(aFile, sh) then begin + mcf:= TutlMCFFile.Create(sh); + try + LoadFromConfig(mcf); + finally + FreeAndNil(mcf); + end; + end; +end; + +procedure TutlSettings.SaveToFile(const aFile: string); +var + sh: IStreamHandle; + mcf: TutlMCFFile; +begin + if vfsManager.CreateFile(aFile, sh) then begin + mcf:= TutlMCFFile.Create(nil); + try + SaveToConfig(mcf); + mcf.SaveToStream(sh); + finally + FreeAndNil(mcf); + end; + end; +end; + +constructor TutlSettings.Create; +begin + inherited Create; + fBlocks:= TBlockList.Create(true); + fRaiseChangedEventOnLoad := true; +end; + +destructor TutlSettings.Destroy; +begin + FreeAndNil(fBlocks); + inherited Destroy; +end; + +initialization + utlSettings := TutlSettings.Create; + +finalization + FreeAndNil(utlSettings); + +end. + diff --git a/uutlStreamHelper.pas b/uutlStreamHelper.pas new file mode 100644 index 0000000..88034ca --- /dev/null +++ b/uutlStreamHelper.pas @@ -0,0 +1,673 @@ +unit uutlStreamHelper; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält Klassen zum lesen und schreiben von Werten in einen Stream + TutlStreamReader - Wrapper für beliebige Streams, handelt Datentypen + TutlStreamWriter - Wrapper für beliebige Streams, handelt Datentypen } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, Contnrs, syncobjs; + +type + TutlFourCC = string[4]; + + { TutlStreamUtility } + + TutlStreamUtility = class + private + FStream: TStream; + FOwnsStream: boolean; + FPositions: TStack; + protected + public + constructor Create(BaseStream: TStream; OwnsStream: Boolean=false); + destructor Destroy; override; + property Stream: TStream read FStream; + procedure Push; + procedure Pop; + procedure Drop; + end; + + { TutlStreamReader } + + TutlStreamReader = class(TutlStreamUtility) + protected + function ReadBuffer(Var Buffer; Size: int64): int64; + public + function ReadFourCC: TutlFourCC; + function CheckFourCC(Correct: TutlFourCC): boolean; + function ReadByte: Byte; + function ReadWord: Word; + function ReadCardinal: Cardinal; + function ReadInteger: Integer; + function ReadInt64: Int64; + function ReadSingle: Single; + function ReadDouble: Double; + function ReadAnsiString: AnsiString; + function ReadLine: AnsiString; + function IsEOF: boolean; + end; + + { TutlStreamWriter } + + TutlStreamWriter = class(TutlStreamUtility) + protected + procedure WriteBuffer(var Data; Size: int64); + public + procedure WriteFourCC(FCC: TutlFourCC); + procedure WriteByte(A: Byte); + procedure WriteWord(A: Word); + procedure WriteCardinal(A: Cardinal); + procedure WriteInteger(A: Integer); + procedure WriteInt64(A: Int64); + procedure WriteSingle(A: Single); + procedure WriteDouble(A: Double); + procedure WriteAnsiString(A: AnsiString); + procedure WriteAnsiBytes(A: AnsiString); + procedure WriteLine(A: AnsiString); + end; + + { TutlReadBufferStream } + + TutlReadBufferStream = class(TStream) + private + FBaseStream: TStream; + FBuffer: Pointer; + FBufferValid: boolean; + FBufferStart, FBufferLen, FBufferAvail: Int64; + FPosition: int64; + protected + function GetSize: Int64; override; + procedure SetSize(const NewSize: Int64); override; + public + constructor Create(const BaseStream: TStream; const BufferSize: Cardinal); + destructor Destroy; override; + function Read(var Buffer; Count: Integer): Integer; override; + function Write(const Buffer; Count: Integer): Integer; override; + function Seek(Offset: Integer; Origin: Word): Integer; override; + end; + + { TutlFIFOStream } + + TutlFIFOStream = class(TStream) + private const MAX_PAGE_SIZE = 4096; + private type + PPage = ^TPage; + TPage = record + Next: PPage; + Data: packed array[0..MAX_PAGE_SIZE-1] of byte; + end; + private + fLockFree: boolean; + fPageFirst, fPageLast: PPage; + fReadPtr, fWritePtr: Cardinal; + fTotalSize: Int64; + fDataLock: TCriticalSection; + protected + function GetSize: Int64; override; + public + constructor Create(const aLockFree: boolean = false); + destructor Destroy; override; + function Read(var Buffer; Count: Longint): Longint; override; + function Reserve(var Buffer; Count: Longint): Longint; + function Discard(Count: Longint): Longint; + function Write(const Buffer; Count: Longint): Longint; override; + function Seek(const {%H-}Offset: Int64; {%H-}Origin: TSeekOrigin): Int64; override; overload; + procedure BeginOperation; + procedure EndOperation; + property LockFree: boolean read fLockFree; + end; + + TutlBase64Decoder = class(TStringStream) + public const + CODE64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + PADDING_CHARACTER = '='; + protected + public + function Read(var Buffer; Count: Longint): Longint; override; + function Decode(const aOutput: TStream): boolean; + constructor Create; + end; + +implementation + +uses SysUtils,RtlConsts, uutlExceptions; + +type + TPositionData = class + Position: Int64; + constructor Create(Pos: Int64); + end; + +constructor TPositionData.Create(Pos: Int64); +begin + inherited Create; + Position:= Pos; +end; + +{ TutlStreamUtility } + +constructor TutlStreamUtility.Create(BaseStream: TStream; OwnsStream: Boolean); +begin + inherited Create; + FStream:= BaseStream; + FOwnsStream:= OwnsStream; + FPositions:= TStack.Create; +end; + +destructor TutlStreamUtility.Destroy; +begin + if FOwnsStream then + FreeAndNil(FStream) + else + FStream:= nil; + while FPositions.AtLeast(1) do + TPositionData(FPositions.Pop).Free; + FreeAndNil(FPositions); + inherited; +end; + +procedure TutlStreamUtility.Pop; +var + p: TPositionData; +begin + p:= TPositionData(FPositions.Pop); + FStream.Position:= p.Position; + p.Free; +end; + +procedure TutlStreamUtility.Drop; +var + p: TPositionData; +begin + p:= TPositionData(FPositions.Pop); + if Assigned(p) then + p.Free; +end; + +procedure TutlStreamUtility.Push; +begin + FPositions.Push(TPositionData.Create(FStream.Position)); +end; + +{ TutlStreamReader } + +function TutlStreamReader.ReadBuffer(var Buffer; Size: int64): int64; +begin + if (FStream.Position + Size > FStream.Size) then + raise EInvalidOperation.Create('stream is to small'); + Result:= FStream.Read(Buffer, Size); +end; + +function TutlStreamReader.ReadFourCC: TutlFourCC; +begin + SetLength(Result, 4); + ReadBuffer(Result[1], 4); +end; + +function TutlStreamReader.CheckFourCC(Correct: TutlFourCC): boolean; +begin + Result:= ReadFourCC=Correct; +end; + +function TutlStreamReader.ReadByte: Byte; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadWord: Word; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadCardinal: Cardinal; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadInteger: Integer; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadInt64: Int64; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadSingle: Single; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadDouble: Double; +begin + ReadBuffer(Result{%H-}, Sizeof(Result)); +end; + +function TutlStreamReader.ReadAnsiString: AnsiString; +begin + SetLength(Result, ReadCardinal); + ReadBuffer(Result[1], Length(Result)); +end; + +function TutlStreamReader.ReadLine: AnsiString; +const + READ_LENGTH = 80; +var + rp, rl: integer; + cp: PAnsiChar; + bpos: Int64; + r: integer; + EOF: Boolean; + + procedure ReadSome; + begin + SetLength(Result, rl + READ_LENGTH); + r:= FStream.Read(Result[rl + 1], READ_LENGTH); + inc(rl, r); + EOF:= r <> READ_LENGTH; + cp:= @Result[rp]; + end; + +begin + Result:= ''; + rl:= 0; + bpos:= FStream.Position; + repeat + rp:= rl + 1; + ReadSome; + while rp <= rl do begin + if cp^ in [#10, #13] then begin + inc(bpos, rp); + // never a second char after #10 + if cp^ = #13 then begin + if (rp = rl) and not EOF then begin + ReadSome; + end; + if (rp <= rl) then begin + inc(cp); + if cp^ = #10 then + inc(bpos); + end; + end; + FStream.Position:= bpos; + SetLength(Result, rp-1); + Exit; + end; + inc(cp); + inc(rp); + end; + until EOF; + SetLength(Result, rl); +end; + +function TutlStreamReader.IsEOF: boolean; +begin + Result:= FStream.Position = FStream.Size; +end; + + +{ TutlStreamWriter } + +procedure TutlStreamWriter.WriteBuffer(var Data; Size: int64); +begin + FStream.Write(Data, Size); +end; + +procedure TutlStreamWriter.WriteFourCC(FCC: TutlFourCC); +begin + WriteBuffer(FCC[1], 4); +end; + +procedure TutlStreamWriter.WriteByte(A: Byte); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteWord(A: Word); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteCardinal(A: Cardinal); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteInteger(A: Integer); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteInt64(A: Int64); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteSingle(A: Single); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteDouble(A: Double); +begin + WriteBuffer(A, SizeOf(a)); +end; + +procedure TutlStreamWriter.WriteAnsiString(A: AnsiString); +begin + WriteCardinal(Length(A)); + WriteBuffer(A[1], Length(a)); +end; + +procedure TutlStreamWriter.WriteAnsiBytes(A: AnsiString); +begin + WriteBuffer(A[1], Length(A)); +end; + +procedure TutlStreamWriter.WriteLine(A: AnsiString); +begin + WriteAnsiBytes(A + sLineBreak); +end; + +{ TutlReadBufferStream } + +constructor TutlReadBufferStream.Create(const BaseStream: TStream; const BufferSize: Cardinal); +begin + inherited Create; + FBaseStream:= BaseStream; + FBufferLen:= BufferSize; + FBuffer:= GetMemory(FBufferLen); + FPosition:= 0; +end; + +destructor TutlReadBufferStream.Destroy; +begin + FBufferValid:= false; + //FBaseStream.Free; + FreeMemory(FBuffer); + inherited; +end; + +function TutlReadBufferStream.Seek(Offset: Integer; Origin: Word): Integer; +begin + case Origin of + soFromBeginning: FPosition := Offset; + soFromCurrent: Inc(FPosition, Offset); + soFromEnd: FPosition := Size + Offset; + end; + Result := FPosition; +end; + +function TutlReadBufferStream.GetSize: Int64; +begin + Result:= FBaseStream.Size; +end; + +procedure TutlReadBufferStream.SetSize(const NewSize: Int64); +begin + FBaseStream.Size:= NewSize; +end; + +function TutlReadBufferStream.Write(const Buffer; Count: Integer): Integer; +begin + FBufferValid:= false; + FBaseStream.Position:= FPosition; + Result:= FBaseStream.Write(Buffer, Count); + FPosition:= FBaseStream.Position; +end; + +function TutlReadBufferStream.Read(var Buffer; Count: Integer): Integer; +var + rp, br, c: Int64; + bp: Pointer; +begin + br:= 0; + bp:= @Buffer; + while (br < Count) and (FPosition<Size) do begin + // Welches Buffer-Segment wird gesucht? + rp:= (FPosition div FBufferLen) * FBufferLen; + // ist das das aktuelle? + if not FBufferValid or (FBufferStart <> rp) then begin + // Segment holen + FBaseStream.Position:= rp; + FBufferAvail:= FBaseStream.Read(FBuffer^, FBufferLen); + FBufferStart:= rp; + FBufferValid:= true; + end; + + // Wie viel Daten daraus brauchen wir bzw. können wir kriegen? + c:= Count - br; + if c > FBufferAvail - (FPosition-FBufferStart) then + c:= FBufferAvail - (FPosition-FBufferStart); + + // das rausholen und buffer weiterschieben + {$IFDEF FPC} + // FPC: kein Cast, direkt mit Pointer in richtiger Größe rechnen + Move(Pointer(FBuffer + (FPosition-FBufferStart))^, bp^, c); + Inc(Bp, c); + {$ELSE} + // Delphi ist eh nur i386, also fix 32bit + Move(Pointer(Cardinal(FBuffer) + (FPosition-FBufferStart))^, bp^, c); + Inc(Cardinal(Bp), c); + {$ENDIF} + Inc(br, c); + Inc(FPosition, c); + end; + Result:= br; +end; + +{ TutlFIFOStream } + +constructor TutlFIFOStream.Create(const aLockFree: boolean); +begin + inherited Create; + fDataLock:= TCriticalSection.Create; + fTotalSize:= 0; + New(fPageFirst); + fPageFirst^.Next:= nil; + fPageLast:= fPageFirst; + fReadPtr:= 0; + fWritePtr:= 0; + fLockFree:= aLockFree; +end; + +destructor TutlFIFOStream.Destroy; +var + p,q: PPage; +begin + BeginOperation; + try + fTotalSize:= 0; + fReadPtr:= 0; + fWritePtr:= 0; + p:= fPageFirst; + while p<>nil do begin + q:= p; + p:= p^.Next; + Dispose(q); + end; + finally + EndOperation; + end; + FreeAndNil(fDataLock); + inherited Destroy; +end; + +function TutlFIFOStream.GetSize: Int64; +begin + Result:= fTotalSize; +end; + +function TutlFIFOStream.Read(var Buffer; Count: Longint): Longint; +begin + BeginOperation; + try + Result:= Reserve(Buffer, Count); + Discard(Result); + finally + EndOperation; + end; +end; + +function TutlFIFOStream.Reserve(var Buffer; Count: Longint): Longint; +var + pbuf: PByteArray; + mx: LongInt; + rp: Int64; + p: PPage; +begin + BeginOperation; + try + pbuf:= @Buffer; + Result:= 0; + rp:= fReadPtr; + p:= fPageFirst; + while Count > 0 do begin + mx:= MAX_PAGE_SIZE - rp; + if mx > Count then mx:= Count; + if (p=fPageLast) and (mx > fWritePtr-rp) then mx:= fWritePtr-rp; + if mx=0 then exit; + Move(p^.Data[rp], pbuf^[Result], mx); + inc(rp, mx); + inc(Result, mx); + Dec(Count, mx); + if rp = MAX_PAGE_SIZE then begin + p:= p^.Next; + rp:= 0; + end; + end; + finally + EndOperation; + end; +end; + +function TutlFIFOStream.Discard(Count: Longint): Longint; +var + mx: LongInt; + n: PPage; +begin + BeginOperation; + try + Result:= 0; + while Count > 0 do begin + mx:= MAX_PAGE_SIZE - fReadPtr; + if mx > Count then mx:= Count; + if (fPageFirst=fPageLast) and (mx > fWritePtr-fReadPtr) then mx:= fWritePtr-fReadPtr; + if mx=0 then exit; + inc(fReadPtr, mx); + inc(Result, mx); + dec(Count, mx); + dec(fTotalSize, mx); + if fReadPtr=MAX_PAGE_SIZE then begin + n:= fPageFirst^.Next; + if Assigned(n) then begin + Dispose(fPageFirst); + fPageFirst:= n; + fReadPtr:= 0; + end;// else kann nicht passieren, das wird mit (mx > fWritePtr-fReadPtr) und (mx=0) schon bedient + end; + end; + finally + EndOperation; + end; +end; + +function TutlFIFOStream.Write(const Buffer; Count: Longint): Longint; +var + mx: LongInt; + pbuf: PByteArray; +begin + BeginOperation; + try + pbuf:= @Buffer; + Result:= 0; + while Count > 0 do begin + mx:= MAX_PAGE_SIZE - fWritePtr; + if mx > Count then mx:= Count; + Move(pbuf^[Result], fPageLast^.Data[fWritePtr], mx); + inc(fWritePtr, mx); + inc(fTotalSize, mx); + dec(Count, mx); + inc(Result, mx); + + if fWritePtr = MAX_PAGE_SIZE then begin + New(fPageLast^.Next); + fPageLast:= fPageLast^.Next; + fPageLast^.Next:= nil; + fWritePtr:= 0; + end; + end; + finally + EndOperation; + end; +end; + +function TutlFIFOStream.Seek(const Offset: Int64; Origin: TSeekOrigin): Int64; +begin + Result:= 0; + raise EStreamError.CreateFmt(SStreamInvalidSeek,[ClassName]); +end; + +procedure TutlFIFOStream.BeginOperation; +begin + if not fLockFree then + fDataLock.Acquire; +end; + +procedure TutlFIFOStream.EndOperation; +begin + if not fLockFree then + fDataLock.Release; +end; + +{ TutlBase64Decoder } + +function TutlBase64Decoder.{%H-}Read(var Buffer; Count: Longint): Longint; +begin + ReadNotImplemented; +end; + +function TutlBase64Decoder.Decode(const aOutput: TStream): boolean; +var + a: Integer; + x: Integer; + b: Integer; + c: AnsiChar; +begin + Result:= false; + a := 0; + b := 0; + Position:= 0; + while inherited Read(c{%H-}, sizeof(c)) = sizeof(c) do begin + x := Pos(c, CODE64) - 1; + if (x >= 0) then begin + b := b * 64 + x; + a := a + 6; + if a >= 8 then begin + a := a - 8; + x := b shr a; + b := b mod (1 shl a); + aOutput.WriteByte(x); + end; + end else if c = PADDING_CHARACTER then + break + else + Exit; + end; + Result:= true; +end; + +constructor TutlBase64Decoder.Create; +begin + inherited Create(''); +end; + + +end. diff --git a/uutlSystemInfo.pas b/uutlSystemInfo.pas new file mode 100644 index 0000000..9a0ae0a --- /dev/null +++ b/uutlSystemInfo.pas @@ -0,0 +1,523 @@ +unit uutlSystemInfo; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält Klassen zum Auslesen von System Informationen (CPU, Grafikkarte, OpenGL) } + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, uutlGenerics + {$IFDEF WINDOWS}, ActiveX, ComObj, variants {$ENDIF}; + +type +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlSystemInfo = class; + TutlSystemInfoList = specialize TutlList<TutlSystemInfo>; + TutlSystemInfo = class(TObject) + private + fName: String; + fValue: String; + fItems: TutlSystemInfoList; + function GetCount: Integer; + function GetItems(const aIndex: Integer): TutlSystemInfo; + public + property Name: String read fName; + property Value: String read fValue; + property Count: Integer read GetCount; + property Items[const aIndex: Integer]: TutlSystemInfo read GetItems; default; + + procedure Update; virtual; + function ToString: String; override; + + constructor Create; virtual; + destructor Destroy; override; + end; + TutlSystemInfoClass = class of TutlSystemInfo; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlOpenGLInfo = class(TutlSystemInfo) + public + procedure Update; override; + end; + + {$IFDEF WINDOWS} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlWmiSystemInfo = class(TutlSystemInfo) + protected type + TStringArr = array of String; + protected + function GetComputer: String; virtual; + function GetNamespace: String; virtual; + function GetUsername: String; virtual; + function GetPassword: String; virtual; + function GetQuery: String; virtual; + function GetProperties: TStringArr; virtual; + function GetSubItemName(const aIndex: Integer): String; virtual; + public + procedure Update; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlProcessorInfo = class(TutlWmiSystemInfo) + private const + PROCESSOR_PROPERTIES: array[0..11] of String = ('AddressWidth', 'Caption', + 'CurrentClockSpeed', 'Description', 'ExtClock', 'Family', 'Manufacturer', + 'MaxClockSpeed', 'Name', 'NumberOfCores', 'NumberOfLogicalProcessors', + 'Version'); + protected + function GetQuery: String; override; + function GetProperties: TStringArr; override; + function GetSubItemName(const aIndex: Integer): String; override; + public + procedure Update; override; + end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TutlVideoControllerInfo = class(TutlWmiSystemInfo) + private const + VIDEO_CONTROLLER_PROPERTIES: array[0..11] of String = ('AdapterRAM', 'Caption', + 'CurrentBitsPerPixel', 'CurrentHorizontalResolution', 'CurrentRefreshRate', + 'CurrentScanMode', 'CurrentVerticalResolution', 'Description', 'DriverDate', + 'DriverVersion', 'Name', 'VideoProcessor'); + protected + function GetQuery: String; override; + function GetProperties: TStringArr; override; + function GetSubItemName(const aIndex: Integer): String; override; + public + procedure Update; override; + end; + {$ENDIF} + +procedure LogSystemInfo(const aClass: TutlSystemInfoClass); + +const + SYTEM_INFO_CLASSES_COUNT = {$IFDEF WINDOWS}2+{$ENDIF}1; + SYTEM_INFO_CLASSES: array[0..SYTEM_INFO_CLASSES_COUNT-1] of TutlSystemInfoClass = ( + {$IFDEF WINDOWS}TutlProcessorInfo, + TutlVideoControllerInfo,{$ENDIF} + TutlOpenGLInfo); + +implementation + +uses + uutlExceptions, math, dglOpenGL, uutlLogger; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function CreateItem(const aName, aValue: String): TutlSystemInfo; +begin + result := TutlSystemInfo.Create; + result.fName := aName; + result.fValue := aValue; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function VariantToStr(const aVariant: Variant): String; +begin + result := ''; + if (TVarData(aVariant).vtype <> varempty) and + (TVarData(aVariant).vtype <> varnull) and + (TVarData(aVariant).vtype <> varerror) then begin + result := aVariant; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure LogSystemInfo(const aClass: TutlSystemInfoClass); +var + info: TutlSystemInfo; + sList: TStringList; + i: Integer; +begin + info := aClass.Create; + sList := TStringList.Create; + try try + info.Update; + sList.Text := info.ToString; + for i := 0 to sList.Count-1 do + utlLogger.Log('SystemInfo', sList[i], []); + except on e: Exception do + utlLogger.Error('SystemInfo', 'Error while logging system info: %s', [e.Message]); + end; + finally + FreeAndNil(info); + FreeAndNil(sList); + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlSystemInfo//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSystemInfo.GetCount: Integer; +begin + result := fItems.Count; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSystemInfo.GetItems(const aIndex: Integer): TutlSystemInfo; +begin + if (aIndex >= 0) and (aIndex < fItems.Count) then + result := fItems[aIndex] + else + raise EOutOfRange.Create(aIndex, 0, fItems.Count-1); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlSystemInfo.Update; +begin +//DUMMY +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlSystemInfo.ToString: String; +var + str: String; + + procedure FillStr(var aStr: String; const aLen: Integer; const aChar: Char = ' '); + begin + while (Length(aStr) < aLen) do + aStr := aStr + aChar; + end; + + procedure WriteItem(const aPrefix: String; const aItem: TutlSystemInfo; const aItemLen: Integer); + var + len, i: Integer; + line: String; + begin + line := aItem.Name + ':'; + if (aItem.Count = 0) then begin + line := line; + FillStr(line, aItemLen + 2); + line := line + aItem.Value; + end; + str := str + aPrefix + line + sLineBreak; + if (aItem.Count > 0) then begin + len := 0; + for i := 0 to aItem.Count-1 do + len := max(len, Length(aItem[i].Name)); + for i := 0 to aItem.Count-1 do + WriteItem(aPrefix+' ', aItem[i], len); + end; + end; + +begin + str := ''; + WriteItem('', self, 0); + result := str; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +constructor TutlSystemInfo.Create; +begin + inherited Create; + fName := ''; + fValue := ''; + fItems := TutlSystemInfoList.Create(true); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +destructor TutlSystemInfo.Destroy; +begin + FreeAndNil(fItems); + inherited Destroy; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlOpenGLInfo//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlOpenGLInfo.Update; + + function AddItem(const aParent: TutlSystemInfo; const aName, aValue: String): TutlSystemInfo; + begin + result := CreateItem(aName, aValue); + aParent.fItems.Add(result); + end; + + function GetInteger(const aName: GLenum): String; + var + i: GLint; + begin + i := 0; + glGetIntegerv(aName, @i); + result := IntToStr(i); + end; + +var + item: TutlSystemInfo; +begin + inherited Update; + fName := 'OpenGL Information'; + fValue := ''; + item := AddItem(self, 'ImplementationBasics', ''); + AddItem(item, 'GL_VENDOR', glGetString(GL_VENDOR)); + AddItem(item, 'GL_RENDER', glGetString(GL_RENDER)); + AddItem(item, 'GL_VERSION', glGetString(GL_VERSION)); + AddItem(item, 'GL_SHADING_LANGUAGE_VERSION', glGetString(GL_SHADING_LANGUAGE_VERSION)); + + item := AddItem(self, 'Basics', ''); + AddItem(item, 'GL_MAX_VIEWPORT_DIMS', GetInteger(GL_MAX_VIEWPORT_DIMS)); + AddItem(item, 'GL_MAX_LIGHTS', GetInteger(GL_MAX_LIGHTS)); + AddItem(item, 'GL_MAX_CLIP_PLANES', GetInteger(GL_MAX_CLIP_PLANES)); + AddItem(item, 'GL_MAX_MODELVIEW_STACK_DEPTH', GetInteger(GL_MAX_MODELVIEW_STACK_DEPTH)); + AddItem(item, 'GL_MAX_PROJECTION_STACK_DEPTH', GetInteger(GL_MAX_PROJECTION_STACK_DEPTH)); + AddItem(item, 'GL_MAX_TEXTURE_STACK_DEPTH', GetInteger(GL_MAX_TEXTURE_STACK_DEPTH)); + AddItem(item, 'GL_MAX_ATTRIB_STACK_DEPTH', GetInteger(GL_MAX_ATTRIB_STACK_DEPTH)); + AddItem(item, 'GL_MAX_COLOR_MATRIX_STACK_DEPTH', GetInteger(GL_MAX_COLOR_MATRIX_STACK_DEPTH)); + AddItem(item, 'GL_MAX_LIST_NESTING', GetInteger(GL_MAX_LIST_NESTING)); + AddItem(item, 'GL_SUBPIXEL_BITS', GetInteger(GL_SUBPIXEL_BITS)); + AddItem(item, 'GL_MAX_ELEMENTS_INDICES', GetInteger(GL_MAX_ELEMENTS_INDICES)); + AddItem(item, 'GL_MAX_ELEMENTS_VERTICES', GetInteger(GL_MAX_ELEMENTS_VERTICES)); + AddItem(item, 'GL_MAX_TEXTURE_UNITS', GetInteger(GL_MAX_TEXTURE_UNITS)); + AddItem(item, 'GL_MAX_TEXTURE_COORDS', GetInteger(GL_MAX_TEXTURE_COORDS)); + AddItem(item, 'GL_MAX_SAMPLE_MASK_WORDS', GetInteger(GL_MAX_SAMPLE_MASK_WORDS)); + AddItem(item, 'GL_MAX_COLOR_TEXTURE_SAMPLES', GetInteger(GL_MAX_COLOR_TEXTURE_SAMPLES)); + AddItem(item, 'GL_MAX_DEPTH_TEXTURE_SAMPLES', GetInteger(GL_MAX_DEPTH_TEXTURE_SAMPLES)); + AddItem(item, 'GL_MAX_INTEGER_SAMPLES', GetInteger(GL_MAX_INTEGER_SAMPLES)); + + item := AddItem(self, 'Textures', ''); + AddItem(item, 'GL_MAX_TEXTURE_SIZE', GetInteger(GL_MAX_TEXTURE_SIZE)); + AddItem(item, 'GL_MAX_3D_TEXTURE_SIZE', GetInteger(GL_MAX_3D_TEXTURE_SIZE)); + AddItem(item, 'GL_MAX_CUBE_MAP_TEXTURE_SIZE', GetInteger(GL_MAX_CUBE_MAP_TEXTURE_SIZE)); + AddItem(item, 'GL_MAX_TEXTURE_LOD_BIAS', GetInteger(GL_MAX_TEXTURE_LOD_BIAS)); + AddItem(item, 'GL_MAX_ARRAY_TEXTURE_LAYERS', GetInteger(GL_MAX_ARRAY_TEXTURE_LAYERS)); + AddItem(item, 'GL_MAX_TEXTURE_BUFFER_SIZE', GetInteger(GL_MAX_TEXTURE_BUFFER_SIZE)); + AddItem(item, 'GL_MAX_RECTANGLE_TEXTURE_SIZE', GetInteger(GL_MAX_RECTANGLE_TEXTURE_SIZE)); + AddItem(item, 'GL_MAX_RENDERBUFFER_SIZE', GetInteger(GL_MAX_RENDERBUFFER_SIZE)); + + item := AddItem(self, 'FrameBuffers', ''); + AddItem(item, 'GL_MAX_DRAW_BUFFERS', GetInteger(GL_MAX_DRAW_BUFFERS)); + AddItem(item, 'GL_MAX_COLOR_ATTACHMENTS', GetInteger(GL_MAX_COLOR_ATTACHMENTS)); + AddItem(item, 'GL_MAX_SAMPLES', GetInteger(GL_MAX_SAMPLES)); + + item := AddItem(self, 'VertexShaderLimits', ''); + AddItem(item, 'GL_MAX_VERTEX_ATTRIBS', GetInteger(GL_MAX_VERTEX_ATTRIBS)); + AddItem(item, 'GL_MAX_VERTEX_UNIFORM_COMPONENTS', GetInteger(GL_MAX_VERTEX_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_VERTEX_UNIFORM_VECTORS', GetInteger(GL_MAX_VERTEX_UNIFORM_VECTORS)); + AddItem(item, 'GL_MAX_VERTEX_UNIFORM_BLOCKS', GetInteger(GL_MAX_VERTEX_UNIFORM_BLOCKS)); + AddItem(item, 'GL_MAX_VERTEX_OUTPUT_COMPONENTS', GetInteger(GL_MAX_VERTEX_OUTPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS', GetInteger(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS)); + + item := AddItem(self, 'FragmentShaderLimits', ''); + AddItem(item, 'GL_MAX_FRAGMENT_UNIFORM_COMPONENTS', GetInteger(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_FRAGMENT_UNIFORM_VECTORS', GetInteger(GL_MAX_FRAGMENT_UNIFORM_VECTORS)); + AddItem(item, 'GL_MAX_FRAGMENT_UNIFORM_BLOCKS', GetInteger(GL_MAX_FRAGMENT_UNIFORM_BLOCKS)); + AddItem(item, 'GL_MAX_FRAGMENT_INPUT_COMPONENTS', GetInteger(GL_MAX_FRAGMENT_INPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_IMAGE_UNITS', GetInteger(GL_MAX_IMAGE_UNITS)); + AddItem(item, 'GL_MAX_FRAGMENT_IMAGE_UNIFORMS', GetInteger(GL_MAX_FRAGMENT_IMAGE_UNIFORMS)); + AddItem(item, 'GL_MIN_PROGRAM_TEXEL_OFFSET', GetInteger(GL_MIN_PROGRAM_TEXEL_OFFSET)); + AddItem(item, 'GL_MAX_PROGRAM_TEXEL_OFFSET', GetInteger(GL_MAX_PROGRAM_TEXEL_OFFSET)); + AddItem(item, 'GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET', GetInteger(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET)); + AddItem(item, 'GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET', GetInteger(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET)); + + item := AddItem(self, 'CombinedFragmentAndVertexShaderLimits', ''); + AddItem(item, 'GL_MAX_UNIFORM_BUFFER_BINDINGS', GetInteger(GL_MAX_UNIFORM_BUFFER_BINDINGS)); + AddItem(item, 'GL_MAX_UNIFORM_BLOCK_SIZE', GetInteger(GL_MAX_UNIFORM_BLOCK_SIZE)); + AddItem(item, 'GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT', GetInteger(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT)); + AddItem(item, 'GL_MAX_COMBINED_UNIFORM_BLOCKS', GetInteger(GL_MAX_COMBINED_UNIFORM_BLOCKS)); + AddItem(item, 'GL_MAX_VARYING_FLOATS', GetInteger(GL_MAX_VARYING_FLOATS)); + AddItem(item, 'GL_MAX_VARYING_COMPONENTS', GetInteger(GL_MAX_VARYING_COMPONENTS)); + AddItem(item, 'GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS', GetInteger(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)); + AddItem(item, 'GL_MAX_SUBROUTINES', GetInteger(GL_MAX_SUBROUTINES)); + AddItem(item, 'GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS', GetInteger(GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS)); + AddItem(item, 'GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS', GetInteger(GL_MAX_COMBINED_VERTEX_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS', GetInteger(GL_MAX_COMBINED_FRAGMENT_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS', GetInteger(GL_MAX_COMBINED_GEOMETRY_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS', GetInteger(GL_MAX_COMBINED_TESS_CONTROL_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS', GetInteger(GL_MAX_COMBINED_TESS_EVALUATION_UNIFORM_COMPONENTS)); + + item := AddItem(self, 'GeometryShaderLimits', ''); + AddItem(item, 'GL_MAX_GEOMETRY_UNIFORM_BLOCKS', GetInteger(GL_MAX_GEOMETRY_UNIFORM_BLOCKS)); + AddItem(item, 'GL_MAX_GEOMETRY_INPUT_COMPONENTS', GetInteger(GL_MAX_GEOMETRY_INPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_GEOMETRY_OUTPUT_COMPONENTS', GetInteger(GL_MAX_GEOMETRY_OUTPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_GEOMETRY_OUTPUT_VERTICES', GetInteger(GL_MAX_GEOMETRY_OUTPUT_VERTICES)); + AddItem(item, 'GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS', GetInteger(GL_MAX_GEOMETRY_TOTAL_OUTPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS', GetInteger(GL_MAX_GEOMETRY_TEXTURE_IMAGE_UNITS)); + AddItem(item, 'GL_MAX_GEOMETRY_SHADER_INVOCATIONS', GetInteger(GL_MAX_GEOMETRY_SHADER_INVOCATIONS)); + + item := AddItem(self, 'TesselationShaderLimits', ''); + AddItem(item, 'GL_MAX_TESS_GEN_LEVEL', GetInteger(GL_MAX_TESS_GEN_LEVEL)); + AddItem(item, 'GL_MAX_PATCH_VERTICES', GetInteger(GL_MAX_PATCH_VERTICES)); + AddItem(item, 'GL_MAX_TESS_PATCH_COMPONENTS', GetInteger(GL_MAX_TESS_PATCH_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS', GetInteger(GL_MAX_TESS_CONTROL_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS', GetInteger(GL_MAX_TESS_CONTROL_UNIFORM_BLOCKS)); + AddItem(item, 'GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS', GetInteger(GL_MAX_TESS_CONTROL_TEXTURE_IMAGE_UNITS)); + AddItem(item, 'GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS', GetInteger(GL_MAX_TESS_CONTROL_TOTAL_OUTPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_CONTROL_INPUT_COMPONENTS', GetInteger(GL_MAX_TESS_CONTROL_INPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS', GetInteger(GL_MAX_TESS_CONTROL_OUTPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS', GetInteger(GL_MAX_TESS_EVALUATION_UNIFORM_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS', GetInteger(GL_MAX_TESS_EVALUATION_UNIFORM_BLOCKS)); + AddItem(item, 'GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS', GetInteger(GL_MAX_TESS_EVALUATION_TEXTURE_IMAGE_UNITS)); + AddItem(item, 'GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS', GetInteger(GL_MAX_TESS_EVALUATION_OUTPUT_COMPONENTS)); + AddItem(item, 'GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS', GetInteger(GL_MAX_TESS_EVALUATION_INPUT_COMPONENTS)); + + item := AddItem(self, 'TransformFeedbackShaderLimits', ''); + AddItem(item, 'GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS', GetInteger(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS)); + AddItem(item, 'GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS', GetInteger(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS)); + AddItem(item, 'GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS', GetInteger(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_COMPONENTS)); + AddItem(item, 'GL_MAX_TRANSFORM_FEEDBACK_BUFFERS', GetInteger(GL_MAX_TRANSFORM_FEEDBACK_BUFFERS)); +end; + +{$IFDEF WINDOWS} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlWmiSystemInfo///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetComputer: String; +begin + result := 'localhost'#0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetNamespace: String; +begin + result := 'root\CIMV2'#0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetUsername: String; +begin + result := #0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetPassword: String; +begin + result := #0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetQuery: String; +begin + result := #0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetProperties: TStringArr; +begin + SetLength(result, 0); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlWmiSystemInfo.GetSubItemName(const aIndex: Integer): String; +begin + result := IntToStr(aIndex); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlWmiSystemInfo.Update; +var + SWbemLocator: OLEVariant; + WMIService: OLEVariant; + WbemObjectSet, WbemObject: OLEVariant; + s: Variant; + pCeltFetched: LongWord; + oEnum: IEnumvariant; + i, j: Integer; + properties: TStringArr; + item: TutlSystemInfo; + + computer: Variant; + namespace: Variant; + username: Variant; + password: Variant; + query: Variant; +const + WBEM_FLAGFORWARDONLY = $00000020; +begin + inherited Update; + fItems.Clear; + CoInitialize(nil); + SWbemLocator := CreateOleObject('WbemScripting.SWbemLocator'); + //WMIService := SWbemLocator.ConnectServer(WbemComputer, 'root\CIMV2', WbemUser, WbemPassword); + computer := GetComputer; + namespace := GetNamespace; + username := GetUsername; + password := GetPassword; + query := GetQuery; + WMIService := SWbemLocator.ConnectServer(computer, namespace, username, password); + WbemObjectSet := WMIService.ExecQuery(query, 'WQL', WBEM_FLAGFORWARDONLY); + oEnum := IUnknown(WbemObjectSet._NewEnum) as IEnumVariant; + i := 0; + properties := GetProperties; + while oEnum.Next(1, WbemObject, pCeltFetched) = 0 do begin + inc(i); + item := TutlSystemInfo.Create; + item.fName := GetSubItemName(i); + fItems.Add(item); + for j := low(properties) to high(properties) do begin + s := properties[j]; + item.fItems.Add(CreateItem( + properties[j], + VariantToStr(WbemObject.Properties_.Item(s).Value))); + end; + end; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//TutlProcessorInfo///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlProcessorInfo.GetQuery: String; +begin + result := 'SELECT * FROM Win32_Processor'#0; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlProcessorInfo.GetProperties: TStringArr; +var + i: Integer; +begin + SetLength(result, Length(PROCESSOR_PROPERTIES)); + for i := low(result) to high(result) do + result[i] := PROCESSOR_PROPERTIES[i]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlProcessorInfo.GetSubItemName(const aIndex: Integer): String; +begin + result := format('Processor %d', [aIndex]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlProcessorInfo.Update; +begin + fName := 'Processor Information'; + fValue := ''; + inherited Update; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlVideoControllerInfo.GetQuery: String; +begin + Result := 'SELECT * FROM Win32_VideoController'; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlVideoControllerInfo.GetProperties: TStringArr; +var + i: Integer; +begin + SetLength(result, Length(VIDEO_CONTROLLER_PROPERTIES)); + for i := low(result) to high(result) do + result[i] := VIDEO_CONTROLLER_PROPERTIES[i]; +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +function TutlVideoControllerInfo.GetSubItemName(const aIndex: Integer): String; +begin + Result := format('Video Controller %d', [aIndex]); +end; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +procedure TutlVideoControllerInfo.Update; +begin + fName := 'Video Controller Information'; + fValue := ''; + inherited Update; +end; +{$ENDIF} + +end. + diff --git a/uutlTiming.pas b/uutlTiming.pas new file mode 100644 index 0000000..4286025 --- /dev/null +++ b/uutlTiming.pas @@ -0,0 +1,84 @@ +unit uutlTiming; + +{ Package: Utils + Prefix: utl - UTiLs + Beschreibung: diese Unit enthält platformunabhängige Methoden für Zeitberechnungen und -messungen + Mehr oder weniger platformunabhängige Timestampgenerierung. + GetTickCount64 ms-Timer + GetMicroTime us-Timer analog php microtime() + Achtung: windows.pp deklariert auch eine GetTickCount64, wird gegen diese gelinkt + läuft die exe nicht mehr unter XP. Unbeding uutlTiming *nach* windows einbinden. } + + +{$mode objfpc}{$H+} + +interface + +uses + Classes + {$ifdef Windows} + , Windows + {$else} + , Unix, BaseUnix + {$endif}; + +function GetTickCount64: QWord; +function GetMicroTime: QWord; + +implementation + +{$IF defined(WINDOWS)} +var + PERF_FREQ: Int64; + +function GetTickCount64: QWord; +begin + // GetTickCount64 is better, but we need to check the Windows version to use it + Result := Windows.GetTickCount(); +end; + +function GetMicroTime: QWord; +var + pc: Int64; +begin + pc := 0; + QueryPerformanceCounter(pc); + Result:= (pc * 1000*1000) div PERF_FREQ; +end; + +initialization + PERF_FREQ := 0; + QueryPerformanceFrequency(PERF_FREQ); + +{$ELSEIF defined(UNIX)} +function GetTickCount64: QWord; +var + tp: TTimeVal; +begin + fpgettimeofday(@tp, nil); + Result := (Int64(tp.tv_sec) * 1000) + (tp.tv_usec div 1000); +end; + +function GetMicroTime: QWord; +var + tp: TTimeVal; +begin + fpgettimeofday(@tp, nil); + Result := (Int64(tp.tv_sec) * 1000*1000) + tp.tv_usec; +end; + +{$ELSE} +function GetTickCount64: QWord; +begin + Result := Trunc(Now * 24 * 60 * 60 * 1000); +end; + +function GetMicroTime: QWord; +begin + Result := Trunc(Now * 24 * 60 * 60 * 1000*1000); +end; + +{$ENDIF} + +end. +