Browse Source

Major Refactor:
- initial home screen gui
- updated collections library
- updated visual grid library
- updated common library

Herman Schoenfeld 7 years ago
parent
commit
853dd45232
40 changed files with 5242 additions and 589 deletions
  1. 56 45
      PascalCoinWallet.lpi
  2. 1 1
      Units/Forms/UCTRLBanner.lfm
  3. 1 1
      Units/Forms/UCTRLBanner.pas
  4. 1 1
      Units/Forms/UCTRLSyncronization.pas
  5. 159 69
      Units/Forms/UCTRLWallet.lfm
  6. 208 13
      Units/Forms/UCTRLWallet.pas
  7. 1 1
      Units/Forms/UFRMAbout.pas
  8. 1 1
      Units/Forms/UFRMAccountExplorer.pas
  9. 1 1
      Units/Forms/UFRMAccountSelect.pas
  10. 1 1
      Units/Forms/UFRMBlockExplorer.pas
  11. 1 1
      Units/Forms/UFRMLogs.pas
  12. 6 5
      Units/Forms/UFRMMainForm.lfm
  13. 12 4
      Units/Forms/UFRMMainForm.pas
  14. 1 1
      Units/Forms/UFRMMemoText.pas
  15. 1 1
      Units/Forms/UFRMMessages.pas
  16. 1 1
      Units/Forms/UFRMNodes.pas
  17. 1 1
      Units/Forms/UFRMNodesIp.pas
  18. 1 1
      Units/Forms/UFRMOperation.pas
  19. 1 1
      Units/Forms/UFRMOperationExplorer.pas
  20. 1 1
      Units/Forms/UFRMPascalCoinWalletConfig.pas
  21. 3 7
      Units/Forms/UFRMPayloadDecoder.pas
  22. 1 1
      Units/Forms/UFRMPendingOperations.pas
  23. 1 1
      Units/Forms/UFRMWalletKeys.pas
  24. 1 3
      Units/Forms/UUserInterface.pas
  25. 11 5
      Units/PascalCoin/UAccounts.pas
  26. 99 27
      Units/PascalCoin/UBlockChain.pas
  27. 5 0
      Units/PascalCoin/UConst.pas
  28. 519 0
      Units/PascalCoin/UDataSources.pas
  29. 9 8
      Units/PascalCoin/UNode.pas
  30. 7 0
      Units/PascalCoin/UTime.pas
  31. 625 0
      Units/Utils/UCache.pas
  32. 689 0
      Units/Utils/UCommon.Collections.pas
  33. 1035 0
      Units/Utils/UCommon.Data.pas
  34. 175 0
      Units/Utils/UCommon.UI.pas
  35. 968 186
      Units/Utils/UCommon.pas
  36. 431 123
      Units/Utils/UVisualGrid.pas
  37. 2 2
      Units/Utils/UWizard.lfm
  38. 16 16
      Units/Utils/UWizard.pas
  39. 187 59
      Units/Utils/generics.collections.pas
  40. 2 0
      Units/Utils/generics.dictionaries.inc

+ 56 - 45
PascalCoinWallet.lpi

@@ -34,7 +34,7 @@
         <PackageName Value="LCL"/>
         <PackageName Value="LCL"/>
       </Item1>
       </Item1>
     </RequiredPackages>
     </RequiredPackages>
-    <Units Count="77">
+    <Units Count="79">
       <Unit0>
       <Unit0>
         <Filename Value="PascalCoinWallet.dpr"/>
         <Filename Value="PascalCoinWallet.dpr"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
@@ -276,155 +276,163 @@
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit47>
       </Unit47>
       <Unit48>
       <Unit48>
-        <Filename Value="Units\Utils\UCommonUI.pas"/>
+        <Filename Value="Units\Utils\UCommon.Data.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit48>
       </Unit48>
       <Unit49>
       <Unit49>
-        <Filename Value="Units\Utils\UCommon.pas"/>
+        <Filename Value="Units\PascalCoin\UGPUMining.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit49>
       </Unit49>
       <Unit50>
       <Unit50>
-        <Filename Value="Units\PascalCoin\UGPUMining.pas"/>
+        <Filename Value="Units\PascalCoin\upcdaemon.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit50>
       </Unit50>
       <Unit51>
       <Unit51>
-        <Filename Value="Units\PascalCoin\upcdaemon.pas"/>
+        <Filename Value="Units\PascalCoin\UPoolMinerThreads.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit51>
       </Unit51>
       <Unit52>
       <Unit52>
-        <Filename Value="Units\PascalCoin\UPoolMinerThreads.pas"/>
+        <Filename Value="Units\PascalCoin\UServerApp.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit52>
       </Unit52>
       <Unit53>
       <Unit53>
-        <Filename Value="Units\PascalCoin\UServerApp.pas"/>
+        <Filename Value="Units\PascalCoin\USha256.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit53>
       </Unit53>
       <Unit54>
       <Unit54>
-        <Filename Value="Units\PascalCoin\USha256.pas"/>
+        <Filename Value="Units\Utils\generics.dictionaries.inc"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit54>
       </Unit54>
       <Unit55>
       <Unit55>
-        <Filename Value="Units\Utils\generics.dictionaries.inc"/>
+        <Filename Value="Units\Utils\generics.dictionariesh.inc"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit55>
       </Unit55>
       <Unit56>
       <Unit56>
-        <Filename Value="Units\Utils\generics.dictionariesh.inc"/>
+        <Filename Value="Units\Forms\UFRMAccountInfo.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
+        <HasResources Value="True"/>
       </Unit56>
       </Unit56>
       <Unit57>
       <Unit57>
-        <Filename Value="Units\Forms\UFRMAccountInfo.pas"/>
+        <Filename Value="Units\Forms\UFRMMemoText.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
       </Unit57>
       </Unit57>
       <Unit58>
       <Unit58>
-        <Filename Value="Units\Forms\UFRMMemoText.pas"/>
+        <Filename Value="Units\Forms\UFRMSaleAccounts.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
-        <HasResources Value="True"/>
       </Unit58>
       </Unit58>
       <Unit59>
       <Unit59>
-        <Filename Value="Units\Forms\UFRMSaleAccounts.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit59>
-      <Unit60>
         <Filename Value="Units\Forms\UCTRLBanner.pas"/>
         <Filename Value="Units\Forms\UCTRLBanner.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="CTRLBanner"/>
         <ComponentName Value="CTRLBanner"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit60>
-      <Unit61>
+      </Unit59>
+      <Unit60>
         <Filename Value="Units\Forms\UFRMMainForm.pas"/>
         <Filename Value="Units\Forms\UFRMMainForm.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="FRMMainForm"/>
         <ComponentName Value="FRMMainForm"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit61>
-      <Unit62>
+      </Unit60>
+      <Unit61>
         <Filename Value="Units\Forms\UCTRLSyncronization.pas"/>
         <Filename Value="Units\Forms\UCTRLSyncronization.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="CTRLSyncronization"/>
         <ComponentName Value="CTRLSyncronization"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
+      </Unit61>
+      <Unit62>
+        <Filename Value="Units\Utils\UAutoScope.pas"/>
+        <IsPartOfProject Value="True"/>
       </Unit62>
       </Unit62>
       <Unit63>
       <Unit63>
-        <Filename Value="Units\Utils\UAutoScope.pas"/>
+        <Filename Value="Units\Utils\UVisualGrid.inc"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit63>
       </Unit63>
       <Unit64>
       <Unit64>
-        <Filename Value="Units\Utils\UVisualGrid.inc"/>
+        <Filename Value="Units\Utils\UVisualGrid.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit64>
       </Unit64>
       <Unit65>
       <Unit65>
-        <Filename Value="Units\Utils\UVisualGrid.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit65>
-      <Unit66>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_Start.pas"/>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_Start.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="WIZAddKey_Start"/>
         <ComponentName Value="WIZAddKey_Start"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit66>
-      <Unit67>
+      </Unit65>
+      <Unit66>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_GenerateOrImport.pas"/>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_GenerateOrImport.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="WIZAddKey_GenerateOrImport"/>
         <ComponentName Value="WIZAddKey_GenerateOrImport"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit67>
-      <Unit68>
+      </Unit66>
+      <Unit67>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_ImportPubKey.pas"/>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_ImportPubKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="WIZAddKey_ImportPubKey"/>
         <ComponentName Value="WIZAddKey_ImportPubKey"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit68>
-      <Unit69>
+      </Unit67>
+      <Unit68>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_ImportPrivKey.pas"/>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_ImportPrivKey.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="WIZAddKey_ImportPrivKey"/>
         <ComponentName Value="WIZAddKey_ImportPrivKey"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit69>
-      <Unit70>
+      </Unit68>
+      <Unit69>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_EnterName.pas"/>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_EnterName.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="WIZAddKey_EnterName"/>
         <ComponentName Value="WIZAddKey_EnterName"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit70>
-      <Unit71>
+      </Unit69>
+      <Unit70>
         <Filename Value="Units\Forms\UFRMWalletKeys.pas"/>
         <Filename Value="Units\Forms\UFRMWalletKeys.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="FRMWalletKeys"/>
         <ComponentName Value="FRMWalletKeys"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
+      </Unit70>
+      <Unit71>
+        <Filename Value="Units\Forms\Wizards\UWIZAddKey.pas"/>
+        <IsPartOfProject Value="True"/>
       </Unit71>
       </Unit71>
       <Unit72>
       <Unit72>
-        <Filename Value="Units\Forms\Wizards\UWIZAddKey.pas"/>
+        <Filename Value="Units\PascalCoin\USettings.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit72>
       </Unit72>
       <Unit73>
       <Unit73>
-        <Filename Value="Units\PascalCoin\USettings.pas"/>
-        <IsPartOfProject Value="True"/>
-      </Unit73>
-      <Unit74>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_SelectEncryption.pas"/>
         <Filename Value="Units\Forms\Wizards\UWIZAddKey_SelectEncryption.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="WIZAddKey_SelectEncryption"/>
         <ComponentName Value="WIZAddKey_SelectEncryption"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
-      </Unit74>
-      <Unit75>
+      </Unit73>
+      <Unit74>
         <Filename Value="Units\Forms\UCTRLWallet.pas"/>
         <Filename Value="Units\Forms\UCTRLWallet.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
         <ComponentName Value="CTRLWallet"/>
         <ComponentName Value="CTRLWallet"/>
         <HasResources Value="True"/>
         <HasResources Value="True"/>
         <ResourceBaseClass Value="Form"/>
         <ResourceBaseClass Value="Form"/>
+      </Unit74>
+      <Unit75>
+        <Filename Value="Units\PascalCoin\UDataSources.pas"/>
+        <IsPartOfProject Value="True"/>
       </Unit75>
       </Unit75>
       <Unit76>
       <Unit76>
-        <Filename Value="Units\Forms\UGrids.pas"/>
+        <Filename Value="Units\Utils\UCommon.Collections.pas"/>
         <IsPartOfProject Value="True"/>
         <IsPartOfProject Value="True"/>
       </Unit76>
       </Unit76>
+      <Unit77>
+        <Filename Value="Units\Utils\UCommon.UI.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit77>
+      <Unit78>
+        <Filename Value="Units\Utils\UCommon.pas"/>
+        <IsPartOfProject Value="True"/>
+      </Unit78>
     </Units>
     </Units>
   </ProjectOptions>
   </ProjectOptions>
   <CompilerOptions>
   <CompilerOptions>
@@ -449,6 +457,9 @@
       </Optimizations>
       </Optimizations>
     </CodeGeneration>
     </CodeGeneration>
     <Linking>
     <Linking>
+      <Debugging>
+        <DebugInfoType Value="dsDwarf2Set"/>
+      </Debugging>
       <Options>
       <Options>
         <Win32>
         <Win32>
           <GraphicApplication Value="True"/>
           <GraphicApplication Value="True"/>

+ 1 - 1
Units/Forms/UCTRLBanner.lfm

@@ -12,7 +12,7 @@ object CTRLBanner: TCTRLBanner
   Constraints.MinHeight = 80
   Constraints.MinHeight = 80
   Constraints.MinWidth = 512
   Constraints.MinWidth = 512
   OnCreate = FormCreate
   OnCreate = FormCreate
-  LCLVersion = '1.6.4.0'
+  Visible = False
   object imBackground: TImage
   object imBackground: TImage
     Left = 0
     Left = 0
     Height = 81
     Height = 81

+ 1 - 1
Units/Forms/UCTRLBanner.pas

@@ -36,7 +36,7 @@ type
 
 
 implementation
 implementation
 
 
-uses UCommonUI;
+uses UCommon.UI;
 
 
 {$R *.lfm}
 {$R *.lfm}
 
 

+ 1 - 1
Units/Forms/UCTRLSyncronization.pas

@@ -17,7 +17,7 @@ interface
 
 
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
-  StdCtrls, ComCtrls, UCommonUI;
+  StdCtrls, ComCtrls, UCommon.UI;
 
 
 type
 type
 
 

+ 159 - 69
Units/Forms/UCTRLWallet.lfm

@@ -1,79 +1,169 @@
 object CTRLWallet: TCTRLWallet
 object CTRLWallet: TCTRLWallet
   Left = -7
   Left = -7
-  Height = 536
+  Height = 571
   Top = 2
   Top = 2
-  Width = 796
+  Width = 1151
+  ActiveControl = cbAccounts
   BorderStyle = bsNone
   BorderStyle = bsNone
   Caption = 'CTRLWallet'
   Caption = 'CTRLWallet'
-  ClientHeight = 536
-  ClientWidth = 796
+  ClientHeight = 571
+  ClientWidth = 1151
   OnCreate = FormCreate
   OnCreate = FormCreate
-  LCLVersion = '1.6.4.0'
-  object GroupBox1: TGroupBox
-    Left = 32
-    Height = 129
-    Top = 24
-    Width = 400
-    Caption = 'Overview'
-    TabOrder = 0
-  end
-  object gpMyAccounts: TGroupBox
-    Left = 32
-    Height = 340
-    Top = 168
-    Width = 400
-    Anchors = [akTop, akLeft, akBottom]
-    Caption = 'My Accounts'
-    ClientHeight = 320
-    ClientWidth = 396
-    TabOrder = 1
-    object cbMyAccViews: TComboBox
-      Left = 8
-      Height = 23
-      Top = 8
-      Width = 376
-      Anchors = [akTop, akLeft, akRight]
-      ItemHeight = 15
-      ItemIndex = 0
-      Items.Strings = (
-        'Show All'
-        'Show non-zero balances'
-        'Get my first account!'
-      )
-      OnChange = cbMyAccViewsChange
-      TabOrder = 0
-      Text = 'Show All'
-    end
-    object paMyAccContent: TPanel
-      Left = 8
-      Height = 261
-      Top = 40
-      Width = 376
-      Anchors = [akTop, akLeft, akRight, akBottom]
-      BevelOuter = bvNone
-      Caption = 'MY ACCOUNT PANEL'
-      Color = clGreen
-      ParentColor = False
-      TabOrder = 1
+  OnResize = FormResize
+  LCLVersion = '1.8.0.6'
+  Visible = False
+  object PairSplitter1: TPairSplitter
+    Left = 0
+    Height = 571
+    Top = 0
+    Width = 1151
+    Align = alClient
+    Position = 500
+    object PairSplitterSide1: TPairSplitterSide
+      Cursor = crArrow
+      Left = 0
+      Height = 571
+      Top = 0
+      Width = 500
+      ClientWidth = 500
+      ClientHeight = 571
+      object GroupBox1: TGroupBox
+        Left = 8
+        Height = 105
+        Top = 8
+        Width = 484
+        Anchors = [akTop, akLeft, akRight]
+        Caption = 'Overview'
+        ClientHeight = 85
+        ClientWidth = 480
+        TabOrder = 0
+        object Label1: TLabel
+          Left = 16
+          Height = 15
+          Top = 16
+          Width = 58
+          Caption = 'Total PASC'
+          Font.Style = [fsBold]
+          ParentColor = False
+          ParentFont = False
+        end
+        object Label2: TLabel
+          Left = 16
+          Height = 15
+          Top = 48
+          Width = 59
+          Caption = 'Total PASA'
+          Font.Style = [fsBold]
+          ParentColor = False
+          ParentFont = False
+        end
+        object lblTotalPASA: TLabel
+          Left = 360
+          Height = 15
+          Top = 48
+          Width = 108
+          Alignment = taRightJustify
+          Anchors = [akTop, akRight]
+          AutoSize = False
+          Caption = '0'
+          Font.Style = [fsBold]
+          ParentColor = False
+          ParentFont = False
+        end
+        object lblTotalPASC: TLabel
+          Left = 360
+          Height = 15
+          Top = 16
+          Width = 106
+          Alignment = taRightJustify
+          Anchors = [akTop, akRight]
+          AutoSize = False
+          Caption = '0'
+          Font.Style = [fsBold]
+          ParentColor = False
+          ParentFont = False
+        end
+      end
+      object gpMyAccounts: TGroupBox
+        Left = 8
+        Height = 440
+        Top = 120
+        Width = 484
+        Anchors = [akTop, akLeft, akRight, akBottom]
+        Caption = 'My Accounts'
+        ClientHeight = 420
+        ClientWidth = 480
+        TabOrder = 1
+        object cbAccounts: TComboBox
+          Left = 8
+          Height = 23
+          Top = 8
+          Width = 460
+          Anchors = [akTop, akLeft, akRight]
+          ItemHeight = 15
+          ItemIndex = 0
+          Items.Strings = (
+            'Show All'
+            'Show non-zero balances'
+            'Get my first account!'
+          )
+          OnChange = cbAccountsChange
+          TabOrder = 0
+          Text = 'Show All'
+        end
+        object paAccounts: TPanel
+          Left = 8
+          Height = 376
+          Top = 40
+          Width = 460
+          Anchors = [akTop, akLeft, akRight, akBottom]
+          BorderSpacing.Around = 3
+          BevelOuter = bvNone
+          Caption = 'MY ACCOUNT PANEL'
+          ParentColor = False
+          TabOrder = 1
+        end
+      end
     end
     end
-  end
-  object gpRecentOps: TGroupBox
-    Left = 448
-    Height = 484
-    Top = 24
-    Width = 321
-    Anchors = [akTop, akLeft, akRight, akBottom]
-    Caption = 'Recent Operations'
-    ClientHeight = 464
-    ClientWidth = 317
-    TabOrder = 2
-    object cbRecentOpsSelectedAccOnly: TCheckBox
-      Left = 21
-      Height = 19
-      Top = 16
-      Width = 255
-      Caption = 'Show operations from selected account only'
-      TabOrder = 0
+    object PairSplitterSide2: TPairSplitterSide
+      Cursor = crArrow
+      Left = 505
+      Height = 571
+      Top = 0
+      Width = 646
+      ClientWidth = 646
+      ClientHeight = 571
+      object gpRecentOps: TGroupBox
+        Left = 8
+        Height = 552
+        Top = 8
+        Width = 636
+        Anchors = [akTop, akLeft, akRight, akBottom]
+        Caption = 'Recent Operations'
+        ClientHeight = 532
+        ClientWidth = 632
+        TabOrder = 0
+        object cbShowSelectedOps: TCheckBox
+          Left = 8
+          Height = 19
+          Top = 12
+          Width = 255
+          Caption = 'Show operations from selected account only'
+          TabOrder = 0
+        end
+        object paOperations: TPanel
+          Left = 8
+          Height = 488
+          Top = 40
+          Width = 620
+          Anchors = [akTop, akLeft, akRight, akBottom]
+          BorderSpacing.Around = 3
+          BevelOuter = bvNone
+          Caption = 'OPERATIONS PANEL'
+          ParentColor = False
+          TabOrder = 1
+        end
+      end
     end
     end
   end
   end
 end
 end

+ 208 - 13
Units/Forms/UCTRLWallet.pas

@@ -6,69 +6,264 @@ interface
 
 
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
-  ExtCtrls, UVisualGrid, UCommonUI;
+  ExtCtrls, PairSplitter, Buttons, UVisualGrid, UCommon.UI, UDataSources, UNode;
 
 
 type
 type
 
 
   { TCTRLWallet }
   { TCTRLWallet }
 
 
   TCTRLWalletAccountView = (wavAllAccounts, wavMyAccounts, wavFirstAccount);
   TCTRLWalletAccountView = (wavAllAccounts, wavMyAccounts, wavFirstAccount);
+
+  TCTRLWalletDuration = (wd30Days, wdFullHistory);
+
   TCTRLWallet = class(TApplicationForm)
   TCTRLWallet = class(TApplicationForm)
-    cbMyAccViews: TComboBox;
-    cbRecentOpsSelectedAccOnly: TCheckBox;
-    GroupBox1: TGroupBox;
+    cbAccounts: TComboBox;
+    cbShowSelectedOps: TCheckBox;
     gpMyAccounts: TGroupBox;
     gpMyAccounts: TGroupBox;
     gpRecentOps: TGroupBox;
     gpRecentOps: TGroupBox;
-    paMyAccContent: TPanel;
-    procedure cbMyAccViewsChange(Sender: TObject);
+    GroupBox1: TGroupBox;
+    Label1: TLabel;
+    Label2: TLabel;
+    lblTotalPASA: TLabel;
+    lblTotalPASC: TLabel;
+    PairSplitter1: TPairSplitter;
+    PairSplitterSide1: TPairSplitterSide;
+    PairSplitterSide2: TPairSplitterSide;
+    paAccounts: TPanel;
+    paOperations: TPanel;
+    procedure cbAccountsChange(Sender: TObject);
+    procedure cmbDurationChange(Sender: TObject);
     procedure FormCreate(Sender: TObject);
     procedure FormCreate(Sender: TObject);
+    procedure FormResize(Sender: TObject);
   private
   private
+    FNodeNotifyEvents : TNodeNotifyEvents;
     FAccountsView : TCTRLWalletAccountView;
     FAccountsView : TCTRLWalletAccountView;
+    FDuration : TCTRLWalletDuration;
     FAccountsGrid : TVisualGrid;
     FAccountsGrid : TVisualGrid;
+    FOperationsGrid : TVisualGrid;
+    FAccountsDataSource : TUserAccountsDataSource;
+    FOperationsDataSource : TAccountsOperationsDataSource;
     procedure SetAccountsView(view: TCTRLWalletAccountView);
     procedure SetAccountsView(view: TCTRLWalletAccountView);
+    procedure SetDuration(const ADuration: TCTRLWalletDuration);
   protected
   protected
     procedure ActivateFirstTime; override;
     procedure ActivateFirstTime; override;
+    procedure OnNodeBlocksChanged(Sender: TObject);
+    procedure OnNodeNewOperation(Sender: TObject);
+    procedure OnAccountsUpdated(Sender: TObject);
+    procedure OnAccountsSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
+    procedure OnOperationSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
+    procedure OnAccountsGridColumnInitialize(Sender: TObject; AColIndex:Integer; AColumn: TVisualColumn);
+    procedure OnOperationsGridColumnInitialize(Sender: TObject; AColIndex:Integer; AColumn: TVisualColumn);
   public
   public
+    property Duration : TCTRLWalletDuration read FDuration write SetDuration;
     property AccountsView : TCTRLWalletAccountView read FAccountsView write SetAccountsView;
     property AccountsView : TCTRLWalletAccountView read FAccountsView write SetAccountsView;
   end;
   end;
 
 
 implementation
 implementation
 
 
-{$R *.lfm}
+uses
+  UUserInterface, UAccounts, UBlockChain, UCommon, UAutoScope, Generics.Collections;
 
 
-uses UGrids;
+{$R *.lfm}
 
 
 { TCTRLWallet }
 { TCTRLWallet }
 
 
 procedure TCTRLWallet.FormCreate(Sender: TObject);
 procedure TCTRLWallet.FormCreate(Sender: TObject);
+var cmbDuration : TComboBox;
 begin
 begin
+  // event registrations
+  FNodeNotifyEvents := TNodeNotifyEvents.Create (self);
+  FNodeNotifyEvents.OnBlocksChanged := OnNodeBlocksChanged;
+  FNodeNotifyEvents.OnOperationsChanged := OnNodeNewOperation;
+
+  // data sources
+  FAccountsDataSource := TUserAccountsDataSource.Create(Self);
+  FOperationsDataSource:= TAccountsOperationsDataSource.Create(Self);
+
+  // grids
   FAccountsGrid := TVisualGrid.Create(Self);
   FAccountsGrid := TVisualGrid.Create(Self);
-  FAccountsGrid.DataSource := TMyAccountDataSource.Create(FAccountsGrid);
+  FAccountsGrid.SortMode := smMultiColumn;
+  FAccountsGrid.FetchDataInThread:= true;
+  FAccountsGrid.AutoPageSize:= true;
+  FAccountsGrid.SelectionType:= stMultiRow;
+  FAccountsGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone];
+  FAccountsGrid.DefaultColumnWidths := TArray<Integer>.Create(
+    100,                   // Account
+    CT_VISUALGRID_STRETCH, // Name
+    100                    // Balance
+  );
+  FAccountsGrid.OnColumnInitialize:= OnAccountsGridColumnInitialize;
+  FAccountsGrid.OnSelection := OnAccountsSelected;
+  FAccountsGrid.OnFinishedUpdating := OnAccountsUpdated;
+
+  FOperationsGrid := TVisualGrid.Create(Self);
+  FOperationsGrid.SortMode := smMultiColumn;
+  FOperationsGrid.FetchDataInThread:= true;
+  FOperationsGrid.AutoPageSize:= true;
+  FOperationsGrid.SelectionType:= stRow;
+  FOperationsGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone];
+  FOperationsGrid.DefaultColumnWidths := TArray<Integer>.Create(
+    130,                   // Time
+    CT_VISUALGRID_DEFAULT, // Block
+    100,                   // Account
+    150,                   // Type
+    130,                   // Amount
+    CT_VISUALGRID_DEFAULT, // Fee
+    100,                   // Balance
+    CT_VISUALGRID_DEFAULT, // Payload
+    80,                    // OPHASH
+    CT_VISUALGRID_STRETCH  // Description (stretch)
+  );
+  FOperationsGrid.OnColumnInitialize:= OnOperationsGridColumnInitialize;
+  FOperationsGrid.OnSelection := OnOperationSelected;
+  FOperationsGrid.Caption.Alignment:= taCenter;
+  FOperationsGrid.Caption.Text := 'All Account Operations';
+  FOperationsGrid.Caption.Visible := true;
+  cmbDuration := TComboBox.Create(FOperationsGrid);
+  FOperationsGrid.WidgetControl := cmbDuration;
+  cmbDuration.Items.BeginUpdate;
+  try
+    cmbDuration.AddItem('30 Days', TObject(wd30Days));
+    cmbDuration.AddItem('Maximum', TObject(wdFullHistory));
+  finally
+    cmbDuration.Items.EndUpdate;
+  end;
+  cmbDuration.OnChange:=cmbDurationChange;
+
   AccountsView := wavMyAccounts;
   AccountsView := wavMyAccounts;
+  paOperations.AddControlDockCenter(FOperationsGrid);
+  Duration := wd30Days;
+end;
+
+procedure TCTRLWallet.FormResize(Sender: TObject);
+begin
+  // Left hand panel is 50% the size up until a max size of 450
+
 end;
 end;
 
 
 procedure TCTRLWallet.ActivateFirstTime;
 procedure TCTRLWallet.ActivateFirstTime;
 begin
 begin
+
+end;
+
+procedure TCTRLWallet.OnAccountsGridColumnInitialize(Sender: TObject; AColIndex:Integer; AColumn: TVisualColumn);
+begin
+  case AColIndex of
+     2: AColumn.InternalColumn.Alignment := taRightJustify;
+  end;
+end;
+
+
+procedure TCTRLWallet.OnOperationsGridColumnInitialize(Sender: TObject; AColIndex:Integer; AColumn: TVisualColumn);
+begin
+  case AColIndex of
+     4, 5, 6: AColumn.InternalColumn.Alignment := taRightJustify;
+  end;
 end;
 end;
 
 
 procedure TCTRLWallet.SetAccountsView(view: TCTRLWalletAccountView);
 procedure TCTRLWallet.SetAccountsView(view: TCTRLWalletAccountView);
 begin
 begin
-  paMyAccContent.RemoveAllControls(false);
+  paAccounts.RemoveAllControls(false);
   case view of
   case view of
      wavAllAccounts: raise Exception.Create('Not implemented');
      wavAllAccounts: raise Exception.Create('Not implemented');
-     wavMyAccounts: paMyAccContent.AddControlDockCenter(FAccountsGrid);
+     wavMyAccounts: begin
+       FOperationsGrid.DataSource := FOperationsDataSource;
+       FAccountsGrid.DataSource := FAccountsDataSource;
+       FAccountsGrid.Caption.Text := 'My Accounts';
+       paAccounts.AddControlDockCenter(FAccountsGrid);
+       FAccountsGrid.RefreshGrid;
+     end;
      wavFirstAccount: raise Exception.Create('Not implemented');
      wavFirstAccount: raise Exception.Create('Not implemented');
   end;
   end;
 end;
 end;
 
 
-procedure TCTRLWallet.cbMyAccViewsChange(Sender: TObject);
+procedure TCTRLWallet.SetDuration(const ADuration: TCTRLWalletDuration);
+begin
+  FDuration:= ADuration;
+  case FDuration of
+    wd30Days: FOperationsDataSource.TimeSpan := TTimeSpan.FromDays(30);
+    wdFullHistory: FOperationsDataSource.TimeSpan := TTimeSpan.FromDays(10 * 365);
+  end;
+  FOperationsGrid.RefreshGrid;
+end;
+
+procedure TCTRLWallet.OnNodeBlocksChanged(Sender: TObject);
+begin
+  FAccountsGrid.RefreshGrid;
+  SetDuration(FDuration);
+end;
+
+procedure TCTRLWallet.OnNodeNewOperation(Sender: TObject);
+begin
+  FAccountsGrid.RefreshGrid;
+  SetDuration(FDuration);
+end;
+
+procedure TCTRLWallet.OnAccountsUpdated(Sender: TObject);
 begin
 begin
-  case cbMyAccViews.ItemIndex of
+   lblTotalPASC.Caption := TAccountComp.FormatMoney( FAccountsDataSource.Overview.TotalPASC );
+   lblTotalPASA.Caption := Format('%d', [FAccountsDataSource.Overview.TotalPASA] );
+end;
+
+procedure TCTRLWallet.OnAccountsSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
+var
+  row : longint;
+  selectedAccounts : Generics.Collections.TList<Cardinal>;
+  acc : Cardinal;
+  GC : TScoped;
+begin
+  selectedAccounts := GC.AddObject( TList<Cardinal>.Create ) as TList<Cardinal>;
+
+  for row := ASelection.Row to (ASelection.Row + ASelection.RowCount - 1) do begin
+    if (TAccountComp.AccountTxtNumberToAccountNumber( FAccountsGrid.Rows[row].Account, acc)) then
+      selectedAccounts.Add(acc);
+  end;
+  FOperationsDataSource.Accounts := selectedAccounts.ToArray;
+  FOperationsGrid.RefreshGrid;
+end;
+
+procedure TCTRLWallet.OnOperationSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
+var
+  row : longint;
+  v : Variant;
+  ophash : AnsiString;
+begin
+  row := ASelection.Row;
+  if (row >= 0) AND (row < FOperationsGrid.RowCount) then begin
+    v := FOperationsGrid.Rows[row];
+    ophash := FOperationsGrid.Rows[row].OPHASH;
+    if TPCOperation.IsValidOperationHash(ophash) then
+      TUserInterface.ShowOperationInfoDialog(self, ophash);
+  end;
+end;
+
+procedure TCTRLWallet.cbAccountsChange(Sender: TObject);
+begin
+  case cbAccounts.ItemIndex of
      0: AccountsView := wavAllAccounts;
      0: AccountsView := wavAllAccounts;
      1: AccountsView := wavMyAccounts;
      1: AccountsView := wavMyAccounts;
      2: AccountsView := wavFirstAccount;
      2: AccountsView := wavFirstAccount;
   end;
   end;
 end;
 end;
 
 
+procedure TCTRLWallet.cmbDurationChange(Sender: TObject);
+var
+  cmbDuration : TComboBox;
+  newDuration : TCTRLWalletDuration;
+begin
+  cmbDuration := Sender as TComboBox;
+  if not Assigned(cmbDuration) then
+    exit;
+
+  case cmbDuration.ItemIndex of
+     0: newDuration := wd30Days;
+     1: newDuration := wdFullHistory;
+  end;
+  if Duration <> newDuration then begin
+    Duration := newDuration;
+    FOperationsGrid.RefreshGrid;
+  end;
+end;
+
 end.
 end.
 
 

+ 1 - 1
Units/Forms/UFRMAbout.pas

@@ -22,7 +22,7 @@ interface
 uses
 uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
-  Dialogs, ExtCtrls, StdCtrls, Buttons, UCommonUI;
+  Dialogs, ExtCtrls, StdCtrls, Buttons, UCommon.UI;
 
 
 type
 type
 
 

+ 1 - 1
Units/Forms/UFRMAccountExplorer.pas

@@ -17,7 +17,7 @@ interface
 
 
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ComCtrls,
-  ExtCtrls, StdCtrls, Buttons, Grids, Menus, UCommonUI,
+  ExtCtrls, StdCtrls, Buttons, Grids, Menus, UCommon.UI,
   UGridUtils, UNode, UAccounts, UBlockChain;
   UGridUtils, UNode, UAccounts, UBlockChain;
 
 
 type
 type

+ 1 - 1
Units/Forms/UFRMAccountSelect.pas

@@ -22,7 +22,7 @@ interface
 uses
 uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
-  Dialogs, UAccounts, Grids, StdCtrls, Buttons, ExtCtrls, UCommonUI,
+  Dialogs, UAccounts, Grids, StdCtrls, Buttons, ExtCtrls, UCommon.UI,
   UWallet, UNode, UGridUtils, UConst, UThread;
   UWallet, UNode, UGridUtils, UConst, UThread;
 
 
 const
 const

+ 1 - 1
Units/Forms/UFRMBlockExplorer.pas

@@ -18,7 +18,7 @@ interface
 
 
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
-  StdCtrls, Grids, Menus, UCommonUI, UGridUtils;
+  StdCtrls, Grids, Menus, UCommon.UI, UGridUtils;
 
 
 type
 type
 
 

+ 1 - 1
Units/Forms/UFRMLogs.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
     LCLIntf,
     LCLIntf,
     SysUtils, Classes, Graphics, Controls, Forms,
     SysUtils, Classes, Graphics, Controls, Forms,
-    Dialogs, ExtCtrls,  StdCtrls, UCommonUI,
+    Dialogs, ExtCtrls,  StdCtrls, UCommon.UI,
     ULog, UBlockChain;
     ULog, UBlockChain;
 
 
 type
 type

+ 6 - 5
Units/Forms/UFRMMainForm.lfm

@@ -23,6 +23,7 @@ object FRMMainForm: TFRMMainForm
   Position = poScreenCenter
   Position = poScreenCenter
   ShowHint = True
   ShowHint = True
   LCLVersion = '1.8.0.6'
   LCLVersion = '1.8.0.6'
+  Visible = False
   object paLogoPanel: TPanel
   object paLogoPanel: TPanel
     Left = 0
     Left = 0
     Height = 80
     Height = 80
@@ -45,7 +46,7 @@ object FRMMainForm: TFRMMainForm
   end
   end
   object paSyncPanel: TPanel
   object paSyncPanel: TPanel
     Left = 0
     Left = 0
-    Height = 505
+    Height = 477
     Top = 80
     Top = 80
     Width = 865
     Width = 865
     Align = alClient
     Align = alClient
@@ -62,7 +63,7 @@ object FRMMainForm: TFRMMainForm
   end
   end
   object paWalletPanel: TPanel
   object paWalletPanel: TPanel
     Left = 0
     Left = 0
-    Height = 505
+    Height = 477
     Top = 80
     Top = 80
     Width = 865
     Width = 865
     Align = alClient
     Align = alClient
@@ -79,8 +80,8 @@ object FRMMainForm: TFRMMainForm
   end
   end
   object sbStatusBar: TStatusBar
   object sbStatusBar: TStatusBar
     Left = 0
     Left = 0
-    Height = 15
-    Top = 585
+    Height = 23
+    Top = 557
     Width = 865
     Width = 865
     Panels = <    
     Panels = <    
       item
       item
@@ -109,7 +110,7 @@ object FRMMainForm: TFRMMainForm
     AnchorSideBottom.Side = asrBottom
     AnchorSideBottom.Side = asrBottom
     Left = 778
     Left = 778
     Height = 15
     Height = 15
-    Top = 585
+    Top = 561
     Width = 70
     Width = 70
     Align = alNone
     Align = alNone
     Anchors = [akTop, akRight]
     Anchors = [akTop, akRight]

+ 12 - 4
Units/Forms/UFRMMainForm.pas

@@ -21,13 +21,14 @@ uses
   ULog,
   ULog,
   UBlockChain, UNode, UGridUtils, UAccounts, Menus,
   UBlockChain, UNode, UGridUtils, UAccounts, Menus,
   UNetProtocol, UCrypto, Buttons, ActnList, UPoolMining,
   UNetProtocol, UCrypto, Buttons, ActnList, UPoolMining,
-  UCTRLBanner, UCTRLWallet, UCTRLSyncronization, UCommon, UCommonUI;
+  UCTRLBanner, UCTRLWallet, UCTRLSyncronization, UCommon, UCommon.UI;
 
 
 const
 const
   CM_PC_FinishedLoadingDatabase = WM_USER + 1;
   CM_PC_FinishedLoadingDatabase = WM_USER + 1;
   CM_PC_WalletKeysChanged = WM_USER + 2;
   CM_PC_WalletKeysChanged = WM_USER + 2;
   CM_PC_ConnectivityChanged = WM_USER + 3;
   CM_PC_ConnectivityChanged = WM_USER + 3;
   CM_PC_Terminate = WM_USER + 4;
   CM_PC_Terminate = WM_USER + 4;
+  CM_PC_ModeChanged  = WM_USER + 5;
 
 
 type
 type
 
 
@@ -99,6 +100,7 @@ type
     procedure CM_WalletChanged(var Msg: TMessage); message CM_PC_WalletKeysChanged;
     procedure CM_WalletChanged(var Msg: TMessage); message CM_PC_WalletKeysChanged;
     procedure CM_ConnectivityChanged(var Msg: TMessage); message CM_PC_ConnectivityChanged;
     procedure CM_ConnectivityChanged(var Msg: TMessage); message CM_PC_ConnectivityChanged;
     procedure CM_Terminate(var Msg: TMessage); message CM_PC_Terminate;
     procedure CM_Terminate(var Msg: TMessage); message CM_PC_Terminate;
+    procedure CM_ModeChanged(var Msg: TMessage); message CM_PC_ModeChanged;
     procedure OnConnectivityChanged(Sender: TObject);
     procedure OnConnectivityChanged(Sender: TObject);
     procedure OnWalletChanged(Sender: TObject);
     procedure OnWalletChanged(Sender: TObject);
   protected
   protected
@@ -231,10 +233,18 @@ end;
 
 
 procedure TFRMMainForm.SetMode(AMode: TFRMMainFormMode);
 procedure TFRMMainForm.SetMode(AMode: TFRMMainFormMode);
 var nestedForm : TForm;
 var nestedForm : TForm;
+begin
+  if FMode = AMode then
+    exit;
+  FMode := AMode;
+  PostMessage(self.Handle, CM_PC_ModeChanged, 0, 0);
+end;
+
+procedure TFRMMainForm.CM_ModeChanged(var Msg: TMessage);
 begin
 begin
   try
   try
     FUILock.Acquire;
     FUILock.Acquire;
-    case AMode of
+    case FMode of
       wmWallet: begin
       wmWallet: begin
         if NOT Assigned(FWalletControl) then begin
         if NOT Assigned(FWalletControl) then begin
           FWalletControl := TCTRLWallet.Create(self);
           FWalletControl := TCTRLWallet.Create(self);
@@ -242,12 +252,10 @@ begin
         end;
         end;
         paWalletPanel.Visible := true;
         paWalletPanel.Visible := true;
         paSyncPanel.Visible := false;
         paSyncPanel.Visible := false;
-        FMode := AMode;
       end;
       end;
       wmSync: begin
       wmSync: begin
         paSyncPanel.Visible := true;
         paSyncPanel.Visible := true;
         paWalletPanel.Visible := false;;
         paWalletPanel.Visible := false;;
-        FMode := AMode;
       end;
       end;
       else raise Exception.Create('[Internal Error] - TFRMMainForm.SetMode: unsupported mode passed');
       else raise Exception.Create('[Internal Error] - TFRMMainForm.SetMode: unsupported mode passed');
     end;
     end;

+ 1 - 1
Units/Forms/UFRMMemoText.pas

@@ -7,7 +7,7 @@ interface
 uses
 uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
-  Dialogs, StdCtrls, Buttons, ExtCtrls, UCommonUI;
+  Dialogs, StdCtrls, Buttons, ExtCtrls, UCommon.UI;
 
 
 type
 type
   TFRMMemoText = class(TApplicationForm)
   TFRMMemoText = class(TApplicationForm)

+ 1 - 1
Units/Forms/UFRMMessages.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   LCLIntf, LCLType,
   LCLIntf, LCLType,
   SysUtils, Classes, Graphics, Controls, Forms,
   SysUtils, Classes, Graphics, Controls, Forms,
-  Dialogs, StdCtrls, Menus, UCommonUI,
+  Dialogs, StdCtrls, Menus, UCommon.UI,
   UNode, UNetProtocol, UCrypto, UFRMMainForm,UConst;
   UNode, UNetProtocol, UCrypto, UFRMMainForm,UConst;
 
 
 type
 type

+ 1 - 1
Units/Forms/UFRMNodes.pas

@@ -18,7 +18,7 @@ interface
 uses
 uses
   LCLIntf, LCLType,
   LCLIntf, LCLType,
   Messages, SysUtils, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Classes, Graphics, Controls, Forms,
-  Dialogs, ExtCtrls, StdCtrls, LMessages, UCommonUI,
+  Dialogs, ExtCtrls, StdCtrls, LMessages, UCommon.UI,
   ULog,  UBlockChain, UNode, Menus,  UNetProtocol;
   ULog,  UBlockChain, UNode, Menus,  UNetProtocol;
 
 
 Const
 Const

+ 1 - 1
Units/Forms/UFRMNodesIp.pas

@@ -10,7 +10,7 @@ uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Dialogs, StdCtrls, Buttons,
   Dialogs, StdCtrls, Buttons,
-  UCommonUI;
+  UCommon.UI;
 
 
 type
 type
   TFRMNodesIp = class(TApplicationForm)
   TFRMNodesIp = class(TApplicationForm)

+ 1 - 1
Units/Forms/UFRMOperation.pas

@@ -22,7 +22,7 @@ interface
 uses
 uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
-  Dialogs, StdCtrls, UCommonUI,
+  Dialogs, StdCtrls, UCommon.UI,
   UNode, UWallet, UCrypto, Buttons, UBlockChain,
   UNode, UWallet, UCrypto, Buttons, UBlockChain,
   UAccounts, UFRMAccountSelect, ActnList, ComCtrls, Types, UCommon;
   UAccounts, UFRMAccountSelect, ActnList, ComCtrls, Types, UCommon;
 
 

+ 1 - 1
Units/Forms/UFRMOperationExplorer.pas

@@ -18,7 +18,7 @@ interface
 
 
 uses
 uses
     LCLIntf, LCLType, SysUtils, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Grids, Menus, Classes,
     LCLIntf, LCLType, SysUtils, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Grids, Menus, Classes,
-    UCommonUI, UGridUtils, UConst;
+    UCommon.UI, UGridUtils, UConst;
 
 
 type
 type
 
 

+ 1 - 1
Units/Forms/UFRMPascalCoinWalletConfig.pas

@@ -20,7 +20,7 @@ interface
 uses
 uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
-  Dialogs, StdCtrls, Buttons, ComCtrls, UCommonUI;
+  Dialogs, StdCtrls, Buttons, ComCtrls, UCommon.UI;
 
 
 const
 const
     CM_PC_WalletKeysChanged = WM_USER + 1;
     CM_PC_WalletKeysChanged = WM_USER + 1;

+ 3 - 7
Units/Forms/UFRMPayloadDecoder.pas

@@ -22,7 +22,7 @@ interface
 uses
 uses
   LCLIntf, LCLType, LMessages,
   LCLIntf, LCLType, LMessages,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
   Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
-  Dialogs, StdCtrls, UCommonUI,
+  Dialogs, StdCtrls, UCommon.UI,
   UBlockChain, UCrypto, UWallet, Buttons, ComCtrls,UAppParams;
   UBlockChain, UCrypto, UWallet, Buttons, ComCtrls,UAppParams;
 
 
 type
 type
@@ -138,13 +138,9 @@ begin
     exit;
     exit;
   end;
   end;
   try
   try
-    r := TCrypto.HexaToRaw(trim(OpHash));
-    if (r='') then begin
-      raise Exception.Create('Value is not an hexadecimal string');
-    end;
     // Build 2.1.4 new decoder option: Check if OpHash is a posible double spend
     // Build 2.1.4 new decoder option: Check if OpHash is a posible double spend
-    If not TPCOperation.DecodeOperationHash(r,nBlock,nAccount,nN_Operation,md160) then begin
-      raise Exception.Create('Value is not a valid OPHASH because can''t extract Block/Account/N_Operation info');
+    If not TPCOperation.TryParseOperationHash(OpHash,nBlock,nAccount,nN_Operation,md160) then begin
+      raise Exception.Create('Invalid OPHASH');
     end;
     end;
     Case TNode.Node.FindNOperation(nBlock,nAccount,nN_Operation,opr) of
     Case TNode.Node.FindNOperation(nBlock,nAccount,nN_Operation,opr) of
       invalid_params : raise Exception.Create(Format('Not a valid OpHash searching at Block:%d Account:%d N_Operation:%d',[nBlock,nAccount,nN_Operation]));
       invalid_params : raise Exception.Create(Format('Not a valid OpHash searching at Block:%d Account:%d N_Operation:%d',[nBlock,nAccount,nN_Operation]));

+ 1 - 1
Units/Forms/UFRMPendingOperations.pas

@@ -18,7 +18,7 @@ interface
 
 
 uses
 uses
   Classes, Forms, Grids,
   Classes, Forms, Grids,
-  ExtCtrls, StdCtrls, Menus, UCommonUI, UGridUtils;
+  ExtCtrls, StdCtrls, Menus, UCommon.UI, UGridUtils;
 
 
 type
 type
 
 

+ 1 - 1
Units/Forms/UFRMWalletKeys.pas

@@ -15,7 +15,7 @@ interface
 
 
 uses
 uses
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Messages,
   Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls, Messages,
-  Buttons, Menus, LMessages, UCommonUI, UWallet;
+  Buttons, Menus, LMessages, UCommon.UI, UWallet;
 
 
 const
 const
   CM_PC_WalletChanged = WM_USER + 1;
   CM_PC_WalletChanged = WM_USER + 1;

+ 1 - 3
Units/Forms/UUserInterface.pas

@@ -17,7 +17,7 @@ interface
 
 
 uses
 uses
   SysUtils, Classes, Forms, Controls, {$IFDEF WINDOWS}Windows,{$ENDIF} ExtCtrls, Dialogs, LCLType,
   SysUtils, Classes, Forms, Controls, {$IFDEF WINDOWS}Windows,{$ENDIF} ExtCtrls, Dialogs, LCLType,
-  UCommonUI, UBlockChain, UAccounts, UNode, UWallet, UConst, UFolderHelper, UGridUtils, URPC, UPoolMining,
+  UCommon.UI, UBlockChain, UAccounts, UNode, UWallet, UConst, UFolderHelper, UGridUtils, URPC, UPoolMining,
   ULog, UThread, UNetProtocol, UCrypto,
   ULog, UThread, UNetProtocol, UCrypto,
   UFRMMainForm, UCTRLSyncronization, UFRMAccountExplorer, UFRMOperationExplorer, UFRMPendingOperations, UFRMOperation,
   UFRMMainForm, UCTRLSyncronization, UFRMAccountExplorer, UFRMOperationExplorer, UFRMPendingOperations, UFRMOperation,
   UFRMLogs, UFRMMessages, UFRMNodes, UFRMBlockExplorer, UFRMWalletKeys;
   UFRMLogs, UFRMMessages, UFRMNodes, UFRMBlockExplorer, UFRMWalletKeys;
@@ -690,9 +690,7 @@ end;
 
 
 class procedure TUserInterface.ShowWallet;
 class procedure TUserInterface.ShowWallet;
 begin
 begin
-  // TODO - VALIDATION HERE
   FMainForm.Mode := wmWallet;
   FMainForm.Mode := wmWallet;
-  // else ShowError('', 'Wallet is currently unavailable, please wait until processing is finished');
 end;
 end;
 
 
 class procedure TUserInterface.ShowSyncDialog;
 class procedure TUserInterface.ShowSyncDialog;

+ 11 - 5
Units/PascalCoin/UAccounts.pas

@@ -86,8 +86,9 @@ Type
     Class Function EqualAccountInfos(const accountInfo1,accountInfo2 : TAccountInfo) : Boolean;
     Class Function EqualAccountInfos(const accountInfo1,accountInfo2 : TAccountInfo) : Boolean;
     Class Function EqualAccountKeys(const account1,account2 : TAccountKey) : Boolean;
     Class Function EqualAccountKeys(const account1,account2 : TAccountKey) : Boolean;
     Class Function AccountNumberToAccountTxtNumber(account_number : Cardinal) : AnsiString;
     Class Function AccountNumberToAccountTxtNumber(account_number : Cardinal) : AnsiString;
-    Class function AccountTxtNumberToAccountNumber(Const account_txt_number : AnsiString; var account_number : Cardinal) : Boolean;
+    Class function AccountTxtNumberToAccountNumber(Const account_txt_number : AnsiString; out account_number : Cardinal) : Boolean;
     Class function FormatMoney(Money : Int64) : AnsiString;
     Class function FormatMoney(Money : Int64) : AnsiString;
+    Class function FormatMoneyDecimal(Money : Int64) : Single;
     Class Function TxtToMoney(Const moneytxt : AnsiString; var money : Int64) : Boolean;
     Class Function TxtToMoney(Const moneytxt : AnsiString; var money : Int64) : Boolean;
     Class Function AccountKeyFromImport(Const HumanReadable : AnsiString; var account : TAccountKey; var errors : AnsiString) : Boolean;
     Class Function AccountKeyFromImport(Const HumanReadable : AnsiString; var account : TAccountKey; var errors : AnsiString) : Boolean;
     Class Function AccountPublicKeyExport(Const account : TAccountKey) : AnsiString;
     Class Function AccountPublicKeyExport(Const account : TAccountKey) : AnsiString;
@@ -186,7 +187,6 @@ Type
     FAutoAddAll : Boolean;
     FAutoAddAll : Boolean;
     FAccountList : TPCSafeBox;
     FAccountList : TPCSafeBox;
     FOrderedAccountKeysList : TList; // An ordered list of pointers to quickly find account keys in account list
     FOrderedAccountKeysList : TList; // An ordered list of pointers to quickly find account keys in account list
-    Function Find(Const AccountKey: TAccountKey; var Index: Integer): Boolean;
     function GetAccountKeyList(index: Integer): TOrderedCardinalList;
     function GetAccountKeyList(index: Integer): TOrderedCardinalList;
     function GetAccountKey(index: Integer): TAccountKey;
     function GetAccountKey(index: Integer): TAccountKey;
   protected
   protected
@@ -194,6 +194,7 @@ Type
   public
   public
     Constructor Create(AccountList : TPCSafeBox; AutoAddAll : Boolean);
     Constructor Create(AccountList : TPCSafeBox; AutoAddAll : Boolean);
     Destructor Destroy; override;
     Destructor Destroy; override;
+    Function Find(Const AccountKey: TAccountKey; var Index: Integer): Boolean;
     Procedure AddAccountKey(Const AccountKey : TAccountKey);
     Procedure AddAccountKey(Const AccountKey : TAccountKey);
     Procedure RemoveAccountKey(Const AccountKey : TAccountKey);
     Procedure RemoveAccountKey(Const AccountKey : TAccountKey);
     Procedure AddAccounts(Const AccountKey : TAccountKey; const accounts : Array of Cardinal);
     Procedure AddAccounts(Const AccountKey : TAccountKey; const accounts : Array of Cardinal);
@@ -365,7 +366,7 @@ Const
 implementation
 implementation
 
 
 uses
 uses
-  SysUtils, ULog, UOpenSSLdef, UOpenSSL, UAccountKeyStorage;
+  SysUtils, ULog, UOpenSSLdef, UOpenSSL, UAccountKeyStorage, math;
 
 
 { TPascalCoinProtocol }
 { TPascalCoinProtocol }
 
 
@@ -873,7 +874,7 @@ begin
   end;
   end;
 end;
 end;
 
 
-class function TAccountComp.AccountTxtNumberToAccountNumber(const account_txt_number: AnsiString; var account_number: Cardinal): Boolean;
+class function TAccountComp.AccountTxtNumberToAccountNumber(const account_txt_number: AnsiString; out account_number: Cardinal): Boolean;
 Var i : Integer;
 Var i : Integer;
   char1 : AnsiChar;
   char1 : AnsiChar;
   char2 : AnsiChar;
   char2 : AnsiChar;
@@ -919,7 +920,12 @@ end;
 
 
 class function TAccountComp.FormatMoney(Money: Int64): AnsiString;
 class function TAccountComp.FormatMoney(Money: Int64): AnsiString;
 begin
 begin
-  Result := FormatFloat('#,###0.0000',(Money/10000));
+  Result := FormatFloat('#,###0.0000', (Money/10000));
+end;
+
+class function TAccountComp.FormatMoneyDecimal(Money : Int64) : Single;
+begin
+  RoundTo( Money / 10000, -4);
 end;
 end;
 
 
 class function TAccountComp.GetECInfoTxt(const EC_OpenSSL_NID: Word): AnsiString;
 class function TAccountComp.GetECInfoTxt(const EC_OpenSSL_NID: Word): AnsiString;

+ 99 - 27
Units/PascalCoin/UBlockChain.pas

@@ -18,7 +18,7 @@ unit UBlockChain;
 interface
 interface
 
 
 uses
 uses
-  Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs;
+  Classes, UCrypto, UAccounts, ULog, UThread, SyncObjs, SysUtils, Generics.Collections, UCommon;
 {$I config.inc}
 {$I config.inc}
 
 
 {
 {
@@ -165,9 +165,9 @@ Type
     constructor Create; virtual;
     constructor Create; virtual;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; virtual;
     function GetBufferForOpHash(UseProtocolV2 : Boolean): TRawBytes; virtual;
     function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean; virtual; abstract;
     function DoOperation(AccountTransaction : TPCSafeBoxTransaction; var errors: AnsiString): Boolean; virtual; abstract;
-    procedure AffectedAccounts(list : TList); virtual; abstract;
+    procedure AffectedAccounts(list : Classes.TList); virtual; abstract;
     class function OpType: Byte; virtual; abstract;
     class function OpType: Byte; virtual; abstract;
-    Class Function OperationToOperationResume(Block : Cardinal; Operation : TPCOperation; Affected_account_number : Cardinal; var OperationResume : TOperationResume) : Boolean;
+    Class Function OperationToOperationResume(Block : Cardinal; Operation : TPCOperation; Affected_account_number : Cardinal; var OperationResume : TOperationResume) : Boolean; overload;
     function OperationAmount : Int64; virtual; abstract;
     function OperationAmount : Int64; virtual; abstract;
     function OperationFee: UInt64; virtual; abstract;
     function OperationFee: UInt64; virtual; abstract;
     function OperationPayload : TRawBytes; virtual; abstract;
     function OperationPayload : TRawBytes; virtual; abstract;
@@ -186,6 +186,8 @@ Type
     Property HasValidSignature : Boolean read FHasValidSignature;
     Property HasValidSignature : Boolean read FHasValidSignature;
     Class function OperationHash_OLD(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHash_OLD(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHashValid(op : TPCOperation; Block : Cardinal) : TRawBytes;
     Class function OperationHashValid(op : TPCOperation; Block : Cardinal) : TRawBytes;
+    class function IsValidOperationHash(const AOpHash : AnsiString) : Boolean;
+    class function TryParseOperationHash(const AOpHash : AnsiString; var block, account, n_operation: Cardinal; var md160Hash : TRawBytes) : Boolean;
     Class function DecodeOperationHash(Const operationHash : TRawBytes; var block, account,n_operation : Cardinal; var md160Hash : TRawBytes) : Boolean;
     Class function DecodeOperationHash(Const operationHash : TRawBytes; var block, account,n_operation : Cardinal; var md160Hash : TRawBytes) : Boolean;
     Class function EqualOperationHashes(Const operationHash1, operationHash2 : TRawBytes) : Boolean;
     Class function EqualOperationHashes(Const operationHash1, operationHash2 : TRawBytes) : Boolean;
     Class function FinalOperationHashAsHexa(Const operationHash : TRawBytes) : AnsiString;
     Class function FinalOperationHashAsHexa(Const operationHash : TRawBytes) : AnsiString;
@@ -201,7 +203,7 @@ Type
     FOnChanged: TNotifyEvent;
     FOnChanged: TNotifyEvent;
     FTotalAmount : Int64;
     FTotalAmount : Int64;
     FTotalFee : Int64;
     FTotalFee : Int64;
-    Procedure InternalAddOperationToHashTree(list : TList; op : TPCOperation);
+    Procedure InternalAddOperationToHashTree(list : Classes.TList; op : TPCOperation);
   public
   public
     Constructor Create;
     Constructor Create;
     Destructor Destroy; Override;
     Destructor Destroy; Override;
@@ -210,7 +212,8 @@ Type
     Property HashTree : TRawBytes read FHashTree;
     Property HashTree : TRawBytes read FHashTree;
     Function OperationsCount : Integer;
     Function OperationsCount : Integer;
     Function GetOperation(index : Integer) : TPCOperation;
     Function GetOperation(index : Integer) : TPCOperation;
-    Function GetOperationsAffectingAccount(account_number : Cardinal; List : TList) : Integer;
+    Function GetOperationsAffectingAccount(account_number : Cardinal; List : Classes.TList) : Integer;
+    Function GetOperationsAffectingAccounts(const account_numbers : array of Cardinal; const AList: TList<Cardinal>) : Integer;
     Procedure CopyFromHashTree(Sender : TOperationsHashTree);
     Procedure CopyFromHashTree(Sender : TOperationsHashTree);
     Property TotalAmount : Int64 read FTotalAmount;
     Property TotalAmount : Int64 read FTotalAmount;
     Property TotalFee : Int64 read FTotalFee;
     Property TotalFee : Int64 read FTotalFee;
@@ -279,6 +282,7 @@ Type
     function LoadBlockFromStorage(Stream: TStream; var errors: AnsiString): Boolean;
     function LoadBlockFromStorage(Stream: TStream; var errors: AnsiString): Boolean;
     function LoadBlockFromStream(Stream: TStream; var errors: AnsiString): Boolean;
     function LoadBlockFromStream(Stream: TStream; var errors: AnsiString): Boolean;
     //
     //
+    Function GetMinerRewardPsuedoOperation : TOperationResume;
     Function ValidateOperationBlock(var errors : AnsiString) : Boolean;
     Function ValidateOperationBlock(var errors : AnsiString) : Boolean;
     Property IsOnlyOperationBlock : Boolean read FIsOnlyOperationBlock;
     Property IsOnlyOperationBlock : Boolean read FIsOnlyOperationBlock;
     Procedure Lock;
     Procedure Lock;
@@ -292,6 +296,9 @@ Type
     Class Function GetOperationClassByOpType(OpType: Cardinal): TPCOperationClass;
     Class Function GetOperationClassByOpType(OpType: Cardinal): TPCOperationClass;
     Class Function GetFirstBlock : TOperationBlock;
     Class Function GetFirstBlock : TOperationBlock;
     Class Function EqualsOperationBlock(Const OperationBlock1,OperationBlock2 : TOperationBlock):Boolean;
     Class Function EqualsOperationBlock(Const OperationBlock1,OperationBlock2 : TOperationBlock):Boolean;
+    Class Function ConvertTimeSpanToBlockCount(const ASpan : TTimeSpan) : Integer;
+    Class Function ConvertBlockCountToTimeSpan(const ACount : Integer) : TTimeSpan;
+
     //
     //
     Property SafeBoxTransaction : TPCSafeBoxTransaction read FSafeBoxTransaction;
     Property SafeBoxTransaction : TPCSafeBoxTransaction read FSafeBoxTransaction;
     Property OperationsHashTree : TOperationsHashTree read FOperationsHashTree;
     Property OperationsHashTree : TOperationsHashTree read FOperationsHashTree;
@@ -381,7 +388,7 @@ Type
     FUpgradingToV2: Boolean;
     FUpgradingToV2: Boolean;
     FOnLog: TPCBankLog;
     FOnLog: TPCBankLog;
     FBankLock: TPCCriticalSection;
     FBankLock: TPCCriticalSection;
-    FNotifyList : TList;
+    FNotifyList : Classes.TList;
     FStorageClass: TStorageClass;
     FStorageClass: TStorageClass;
     function GetStorage: TStorage;
     function GetStorage: TStorage;
     procedure SetStorageClass(const Value: TStorageClass);
     procedure SetStorageClass(const Value: TStorageClass);
@@ -416,10 +423,10 @@ implementation
 
 
 uses
 uses
   {Messages, }
   {Messages, }
-  SysUtils, Variants, {Graphics,}
+  Variants, {Graphics,}
   {Controls, Forms,}
   {Controls, Forms,}
   Dialogs, {StdCtrls,}
   Dialogs, {StdCtrls,}
-  UTime, UConst, UOpTransaction, UCommon;
+  UTime, UConst, UOpTransaction, UAutoScope;
 
 
 { TPCBank }
 { TPCBank }
 
 
@@ -530,7 +537,7 @@ begin
   FIsRestoringFromFile := False;
   FIsRestoringFromFile := False;
   FOnLog := Nil;
   FOnLog := Nil;
   FSafeBox := TPCSafeBox.Create;
   FSafeBox := TPCSafeBox.Create;
-  FNotifyList := TList.Create;
+  FNotifyList := Classes.TList.Create;
   FLastBlockCache := TPCOperationsComp.Create(Nil);
   FLastBlockCache := TPCOperationsComp.Create(Nil);
   FIsRestoringFromFile:=False;
   FIsRestoringFromFile:=False;
   FUpgradingToV2:=False;
   FUpgradingToV2:=False;
@@ -603,10 +610,10 @@ begin
                 Storage.DeleteBlockChainBlocks(BlocksCount);
                 Storage.DeleteBlockChainBlocks(BlocksCount);
                 break;
                 break;
               end else begin
               end else begin
-                // To prevent continuous saving...
                 {$IFDEF TESTNET}
                 {$IFDEF TESTNET}
                 Storage.SaveBank;
                 Storage.SaveBank;
                 {$ELSE}
                 {$ELSE}
+                // To prevent continuous saving...
                 If (BlocksCount MOD (CT_BankToDiskEveryNBlocks*10))=0 then begin
                 If (BlocksCount MOD (CT_BankToDiskEveryNBlocks*10))=0 then begin
                   Storage.SaveBank;
                   Storage.SaveBank;
                 end;
                 end;
@@ -976,6 +983,16 @@ begin
   inherited;
   inherited;
 end;
 end;
 
 
+Class Function TPCOperationsComp.ConvertTimeSpanToBlockCount(const ASpan : TTimeSpan) : Integer;
+begin
+  Result := Round ( ASpan.TotalSeconds / CT_NewLineSecondsAvg );
+end;
+
+Class Function TPCOperationsComp.ConvertBlockCountToTimeSpan(const ACount : Integer) : TTimeSpan;
+begin
+  Result := TTimeSpan.FromSeconds( CT_NewLineSecondsAvg * ACount );
+end;
+
 class function TPCOperationsComp.EqualsOperationBlock(const OperationBlock1,
 class function TPCOperationsComp.EqualsOperationBlock(const OperationBlock1,
   OperationBlock2: TOperationBlock): Boolean;
   OperationBlock2: TOperationBlock): Boolean;
 begin
 begin
@@ -1424,6 +1441,19 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TPCOperationsComp.GetMinerRewardPsuedoOperation : TOperationResume;
+begin
+   Result := CT_TOperationResume_NUL;
+   Result.valid := true;
+   Result.Block := FOperationBlock.block;
+   Result.time := self.OperationBlock.timestamp;
+   Result.AffectedAccount := FOperationBlock.block * CT_AccountsPerBlock;
+   Result.Amount := self.OperationBlock.reward;
+   Result.Fee := self.OperationBlock.fee;
+   Result.Balance := Result.Amount+Result.Fee;
+   Result.OperationTxt := 'Miner reward';
+end;
+
 function TPCOperationsComp.ValidateOperationBlock(var errors : AnsiString): Boolean;
 function TPCOperationsComp.ValidateOperationBlock(var errors : AnsiString): Boolean;
 Var lastpow : AnsiString;
 Var lastpow : AnsiString;
   i : Integer;
   i : Integer;
@@ -1538,7 +1568,7 @@ Type TOperationHashTreeReg = Record
      POperationHashTreeReg = ^TOperationHashTreeReg;
      POperationHashTreeReg = ^TOperationHashTreeReg;
 
 
 procedure TOperationsHashTree.AddOperationToHashTree(op: TPCOperation);
 procedure TOperationsHashTree.AddOperationToHashTree(op: TPCOperation);
-Var l : TList;
+Var l : Classes.TList;
 begin
 begin
   l := FHashTreeOperations.LockList;
   l := FHashTreeOperations.LockList;
   try
   try
@@ -1549,7 +1579,7 @@ begin
 end;
 end;
 
 
 procedure TOperationsHashTree.ClearHastThree;
 procedure TOperationsHashTree.ClearHastThree;
-var l : TList;
+var l : Classes.TList;
   i : Integer;
   i : Integer;
   P : POperationHashTreeReg;
   P : POperationHashTreeReg;
 begin
 begin
@@ -1575,7 +1605,7 @@ end;
 
 
 procedure TOperationsHashTree.CopyFromHashTree(Sender: TOperationsHashTree);
 procedure TOperationsHashTree.CopyFromHashTree(Sender: TOperationsHashTree);
 Var i : Integer;
 Var i : Integer;
-  lme, lsender : TList;
+  lme, lsender : Classes.TList;
   PSender : POperationHashTreeReg;
   PSender : POperationHashTreeReg;
   lastNE : TNotifyEvent;
   lastNE : TNotifyEvent;
 begin
 begin
@@ -1616,7 +1646,7 @@ begin
 end;
 end;
 
 
 procedure TOperationsHashTree.Delete(index: Integer);
 procedure TOperationsHashTree.Delete(index: Integer);
-Var l : TList;
+Var l : Classes.TList;
   P : POperationHashTreeReg;
   P : POperationHashTreeReg;
   i : Integer;
   i : Integer;
 begin
 begin
@@ -1654,7 +1684,7 @@ begin
 end;
 end;
 
 
 function TOperationsHashTree.GetOperation(index: Integer): TPCOperation;
 function TOperationsHashTree.GetOperation(index: Integer): TPCOperation;
-Var l : TList;
+Var l : Classes.TList;
 begin
 begin
   l := FHashTreeOperations.LockList;
   l := FHashTreeOperations.LockList;
   try
   try
@@ -1664,15 +1694,15 @@ begin
   end;
   end;
 end;
 end;
 
 
-function TOperationsHashTree.GetOperationsAffectingAccount(account_number: Cardinal; List: TList): Integer;
+function TOperationsHashTree.GetOperationsAffectingAccount(account_number: Cardinal; List: Classes.TList): Integer;
   // This function retrieves operations from HashTree that affeccts to an account_number
   // This function retrieves operations from HashTree that affeccts to an account_number
-Var l,intl : TList;
+Var l,intl : Classes.TList;
   i,j : Integer;
   i,j : Integer;
 begin
 begin
   List.Clear;
   List.Clear;
   l := FHashTreeOperations.LockList;
   l := FHashTreeOperations.LockList;
   try
   try
-    intl := TList.Create;
+    intl := Classes.TList.Create;
     try
     try
       for i := 0 to l.Count - 1 do begin
       for i := 0 to l.Count - 1 do begin
         intl.Clear;
         intl.Clear;
@@ -1688,9 +1718,34 @@ begin
   end;
   end;
 end;
 end;
 
 
+Function TOperationsHashTree.GetOperationsAffectingAccounts(const account_numbers : array of Cardinal; const AList: TList<Cardinal>) : Integer;
+var
+  blockOps,legacyList : Classes.TList;
+  i,j : Integer;
+  GC : TScoped;
+begin
+  AList.Clear;
+  blockOps := FHashTreeOperations.LockList;
+  legacyList := GC.AddObject( Classes.TList.Create ) as Classes.TList;
+  try
+    for i := 0 to blockOps.Count - 1 do begin
+      legacyList.Clear;
+      POperationHashTreeReg(blockOps[i])^.Op.AffectedAccounts(legacyList);
+      for j := Low(account_numbers) to High(account_numbers) do
+        if legacyList.IndexOf(TObject(account_numbers[j]))>=0 then begin
+          AList.Add(i);
+          break;
+        end;
+    end;
+    Result := legacyList.Count;
+  finally
+    FHashTreeOperations.UnlockList;
+  end;
+end;
+
 function TOperationsHashTree.IndexOfOperation(op: TPCOperation): Integer;
 function TOperationsHashTree.IndexOfOperation(op: TPCOperation): Integer;
 Var
 Var
-  l : TList;
+  l : Classes.TList;
   OpSha256 : TRawBytes;
   OpSha256 : TRawBytes;
 begin
 begin
   OpSha256 := op.Sha256;
   OpSha256 := op.Sha256;
@@ -1706,7 +1761,7 @@ begin
 end;
 end;
 
 
 function TOperationsHashTree.CountOperationsBySameSignerWithoutFee(account_number: Cardinal): Integer;
 function TOperationsHashTree.CountOperationsBySameSignerWithoutFee(account_number: Cardinal): Integer;
-Var l : TList;
+Var l : Classes.TList;
   i : Integer;
   i : Integer;
 begin
 begin
   Result := 0;
   Result := 0;
@@ -1720,7 +1775,7 @@ begin
   End;
   End;
 end;
 end;
 
 
-procedure TOperationsHashTree.InternalAddOperationToHashTree(list: TList; op: TPCOperation);
+procedure TOperationsHashTree.InternalAddOperationToHashTree(list: Classes.TList; op: TPCOperation);
 Var msCopy : TMemoryStream;
 Var msCopy : TMemoryStream;
   h : TRawBytes;
   h : TRawBytes;
   P : POperationHashTreeReg;
   P : POperationHashTreeReg;
@@ -1802,7 +1857,7 @@ begin
 end;
 end;
 
 
 function TOperationsHashTree.OperationsCount: Integer;
 function TOperationsHashTree.OperationsCount: Integer;
-Var l : TList;
+Var l : Classes.TList;
 begin
 begin
   l := FHashTreeOperations.LockList;
   l := FHashTreeOperations.LockList;
   try
   try
@@ -1815,7 +1870,7 @@ end;
 function TOperationsHashTree.SaveOperationsHashTreeToStream(Stream: TStream; SaveToStorage: Boolean): Boolean;
 function TOperationsHashTree.SaveOperationsHashTreeToStream(Stream: TStream; SaveToStorage: Boolean): Boolean;
 Var c, i, OpType: Cardinal;
 Var c, i, OpType: Cardinal;
   bcop: TPCOperation;
   bcop: TPCOperation;
-  l : TList;
+  l : Classes.TList;
 begin
 begin
   l := FHashTreeOperations.LockList;
   l := FHashTreeOperations.LockList;
   Try
   Try
@@ -2001,6 +2056,23 @@ begin
   end;
   end;
 end;
 end;
 
 
+class function TPCOperation.IsValidOperationHash(const AOpHash : AnsiString) : Boolean;
+var block, account, n_operation: Cardinal; md160Hash : TRawBytes;
+begin
+  Result := TryParseOperationHash(AOpHash, block, account, n_operation, md160Hash);
+end;
+
+class function TPCOperation.TryParseOperationHash(const AOpHash : AnsiString; var block, account, n_operation: Cardinal; var md160Hash : TRawBytes) : Boolean;
+var
+  ophash : TRawBytes;
+begin
+  ophash := TCrypto.HexaToRaw(trim(AOpHash));
+  if Length(ophash) = 0 then
+    Exit(false);
+  If not TPCOperation.DecodeOperationHash(ophash,block,account,n_operation,md160Hash) then
+    Exit(false);
+end;
+
 class function TPCOperation.EqualOperationHashes(const operationHash1,operationHash2: TRawBytes): Boolean;
 class function TPCOperation.EqualOperationHashes(const operationHash1,operationHash2: TRawBytes): Boolean;
   // operationHash1 and operationHash2 must be in RAW format (Not hexadecimal string!)
   // operationHash1 and operationHash2 must be in RAW format (Not hexadecimal string!)
 var b0,b1,b2,r1,r2 : TRawBytes;
 var b0,b1,b2,r1,r2 : TRawBytes;
@@ -2307,7 +2379,7 @@ end;
 procedure TOperationsResumeList.Clear;
 procedure TOperationsResumeList.Clear;
 Var P : POperationResume;
 Var P : POperationResume;
   i : Integer;
   i : Integer;
-  l : TList;
+  l : Classes.TList;
 begin
 begin
   l := FList.LockList;
   l := FList.LockList;
   try
   try
@@ -2322,7 +2394,7 @@ begin
 end;
 end;
 
 
 function TOperationsResumeList.Count: Integer;
 function TOperationsResumeList.Count: Integer;
-Var l : TList;
+Var l : Classes.TList;
 begin
 begin
   l := FList.LockList;
   l := FList.LockList;
   Try
   Try
@@ -2339,7 +2411,7 @@ end;
 
 
 procedure TOperationsResumeList.Delete(index: Integer);
 procedure TOperationsResumeList.Delete(index: Integer);
 Var P : POperationResume;
 Var P : POperationResume;
-  l : TList;
+  l : Classes.TList;
 begin
 begin
   l := FList.LockList;
   l := FList.LockList;
   Try
   Try
@@ -2359,7 +2431,7 @@ begin
 end;
 end;
 
 
 function TOperationsResumeList.GetOperationResume(index: Integer): TOperationResume;
 function TOperationsResumeList.GetOperationResume(index: Integer): TOperationResume;
-Var l : TList;
+Var l : Classes.TList;
 begin
 begin
   l := FList.LockList;
   l := FList.LockList;
   try
   try

+ 5 - 0
Units/PascalCoin/UConst.pas

@@ -98,12 +98,15 @@ Const
   // NetProtocol_Available MUST BE always >= NetProtocol_version
   // NetProtocol_Available MUST BE always >= NetProtocol_version
   CT_NetProtocol_Available: Word = $0006;  // Remember, >= NetProtocol_version !!!
   CT_NetProtocol_Available: Word = $0006;  // Remember, >= NetProtocol_version !!!
 
 
+  CT_V2_AccountStart = CT_Protocol_Upgrade_v2_MinBlock div CT_AccountsPerBlock;
+
   CT_MaxAccountOperationsPerBlockWithoutFee = 1;
   CT_MaxAccountOperationsPerBlockWithoutFee = 1;
 
 
   CT_SafeBoxBankVersion : Word = 3; // Protocol 2 upgraded safebox version from 2 to 3
   CT_SafeBoxBankVersion : Word = 3; // Protocol 2 upgraded safebox version from 2 to 3
 
 
   CT_MagicIdentificator: AnsiString = {$IFDEF PRODUCTION}'PascalCoin'{$ELSE}'PascalCoinTESTNET'{$ENDIF}; //
   CT_MagicIdentificator: AnsiString = {$IFDEF PRODUCTION}'PascalCoin'{$ELSE}'PascalCoinTESTNET'{$ENDIF}; //
 
 
+  CT_PseudoOp_Reward = $0;
   // Value of Operations type in Protocol 1
   // Value of Operations type in Protocol 1
   CT_Op_Transaction = $01;
   CT_Op_Transaction = $01;
   CT_Op_Changekey = $02;
   CT_Op_Changekey = $02;
@@ -115,6 +118,8 @@ Const
   CT_Op_ChangeKeySigned = $07;
   CT_Op_ChangeKeySigned = $07;
   CT_Op_ChangeAccountInfo = $08;
   CT_Op_ChangeAccountInfo = $08;
 
 
+  CT_PseudoOpSubtype_Miner                = 1;
+  CT_PseudoOpSubtype_Developer            = 2;
   CT_OpSubtype_TransactionSender          = 11;
   CT_OpSubtype_TransactionSender          = 11;
   CT_OpSubtype_TransactionReceiver        = 12;
   CT_OpSubtype_TransactionReceiver        = 12;
   CT_OpSubtype_BuyTransactionBuyer        = 13;
   CT_OpSubtype_BuyTransactionBuyer        = 13;

+ 519 - 0
Units/PascalCoin/UDataSources.pas

@@ -0,0 +1,519 @@
+unit UDataSources;
+
+{$mode delphi}
+
+interface
+
+uses
+  Classes, SysUtils, UAccounts, UNode, UBlockchain, UCommon, UConst, UCommon.Data, Generics.Collections, Generics.Defaults, syncobjs;
+
+type
+
+  { TUserAccountsDataSource }
+
+  TUserAccountsDataSource = class(TCustomDataSource<TAccount>)
+    public type
+      TOverview = record
+        TotalPASC : UInt64;
+        TotalPASA : Cardinal;
+      end;
+    protected
+      FLastOverview : TOverview;
+      function GetItemDisposePolicy : TItemDisposePolicy; override;
+      function GetColumns : TTableColumns;  override;
+    public
+      property Overview : TOverview read FLastOverview;
+      function GetSearchCapabilities: TSearchCapabilities; override;
+      procedure FetchAll(const AContainer : TList<TAccount>); override;
+      function GetItemField(constref AItem: TAccount; const AColumnName : utf8string) : Variant; override;
+      procedure DehydrateItem(constref AItem: TAccount; var ATableRow: Variant); override;
+  end;
+
+  { TOperationsDataSourceBase }
+
+  TOperationsDataSourceBase = class(TCustomDataSource<TOperationResume>)
+    private
+      FStart, FEnd : Cardinal;
+      function GetTimeSpan : TTimeSpan;
+      procedure SetTimeSpan(const ASpan : TTimeSpan);
+    protected
+      function GetItemDisposePolicy : TItemDisposePolicy; override;
+      function GetColumns : TTableColumns;  override;
+    public
+      constructor Create(AOwner: TComponent); override;
+      property TimeSpan : TTimeSpan read GetTimeSpan write SetTimeSpan;
+      property StartBlock : Cardinal read FStart write FStart;
+      property EndBlock : Cardinal read FEnd write FEnd;
+      function GetSearchCapabilities: TSearchCapabilities; override;
+      function GetItemField(constref AItem: TOperationResume; const AColumnName : utf8string) : Variant; override;
+      procedure DehydrateItem(constref AItem: TOperationResume; var ATableRow: Variant); override;
+  end;
+
+  { TAccountsOperationsDataSource }
+
+  TAccountsOperationsDataSource = class(TOperationsDataSourceBase)
+    private
+      FAccounts : TSortedHashSet<Cardinal>;
+      function GetAccounts : TArray<Cardinal> ;
+      procedure SetAccounts(const AAccounts : TArray<Cardinal>);
+    public
+      constructor Create(AOwner: TComponent);
+      destructor Destroy;
+      property Accounts : TArray<Cardinal> read GetAccounts write SetAccounts;
+      procedure FetchAll(const AContainer : TList<TOperationResume>); override;
+  end;
+
+  { TPendingOperationsDataSource }
+
+  TPendingOperationsDataSource = class(TOperationsDataSourceBase)
+    public
+      procedure FetchAll(const AContainer : TList<TOperationResume>); override;
+  end;
+
+  { TOperationsDataSource }
+
+  TOperationsDataSource = class(TOperationsDataSourceBase)
+    public
+      procedure FetchAll(const AContainer : TList<TOperationResume>); override;
+  end;
+
+  { TDataSourceTool }
+
+  TDataSourcetool = class
+    class function OperationShortHash(const AOpHash : AnsiString) : AnsiString;
+    class function OperationShortText(const OpType, OpSubType : DWord) : AnsiString;
+    class function AccountKeyShortText(const AText : AnsiString) : AnsiString;
+  end;
+
+implementation
+
+uses UWallet, UUserInterface, UAutoScope, UCommon.Collections, math, UTime;
+
+{ TUserAccountsDataSource }
+
+function TUserAccountsDataSource.GetItemDisposePolicy : TItemDisposePolicy;
+begin
+  Result := idpNone;
+end;
+
+function TUserAccountsDataSource.GetColumns : TTableColumns;
+begin
+  Result := TTableColumns.Create('Account', 'Name', 'Balance');
+end;
+
+function TUserAccountsDataSource.GetSearchCapabilities: TSearchCapabilities;
+begin
+  Result := TSearchCapabilities.Create(
+    TSearchCapability.From('Account', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Name', SORTABLE_TEXT_FILTER),
+    TSearchCapability.From('Balance', SORTABLE_NUMERIC_FILTER)
+  );
+end;
+
+procedure TUserAccountsDataSource.FetchAll(const AContainer : TList<TAccount>);
+var
+  i, keyIndex : integer;
+  acc : TAccount;
+  safeBox : TPCSafeBox;
+  keys : TOrderedAccountKeysList;
+  GC : TScoped;
+begin
+  FLastOverview.TotalPASC := 0;
+  FLastOverview.TotalPASA := 0;
+
+  keys := TWallet.Keys.AccountsKeyList;
+  safeBox := TUserInterface.Node.Bank.SafeBox;
+  safeBox.StartThreadSafe;
+  try
+
+   // load user accounts
+   for i := 0 to safeBox.AccountsCount - 1 do begin
+     acc := safeBox.Account(i);
+     if keys.Find(acc.accountInfo.accountKey, keyIndex) then begin
+       AContainer.Add(acc);
+       FLastOverview.TotalPASC := FLastOverview.TotalPASC + acc.Balance;
+       inc(FLastOverview.TotalPASA);
+     end;
+   end;
+  finally
+   safeBox.EndThreadSave;
+  end;
+end;
+
+function TUserAccountsDataSource.GetItemField(constref AItem: TAccount; const AColumnName : utf8string) : Variant;
+var
+  index : Integer;
+begin
+   if AColumnName = 'Account' then
+     Result := AItem.account
+   else if AColumnName = 'Name' then
+     Result := AItem.name
+   else if AColumnName = 'Balance' then
+     Result := TAccountComp.FormatMoneyDecimal(AItem.Balance)
+   else if AColumnName = 'Key' then begin
+     if TWallet.Keys.AccountsKeyList.Find(AItem.accountInfo.accountKey, index) then
+        Result := TWallet.Keys[index].Name
+     else
+         Result := TAccountComp.AccountPublicKeyExport(AItem.accountInfo.accountKey);
+   end else if AColumnName = 'AccType' then
+     Result := AItem.account_type
+   else if AColumnName = 'State' then
+     Result := AItem.accountInfo.state
+   else if AColumnName = 'Price' then
+     Result := AItem.accountInfo.price
+   else if AColumnName = 'LockedUntil' then
+     Result := AItem.accountInfo.locked_until_block
+   else raise Exception.Create(Format('Field not found [%s]', [AColumnName]));
+end;
+
+procedure TUserAccountsDataSource.DehydrateItem(constref AItem: TAccount; var ATableRow: Variant);
+var
+  index : Integer;
+begin
+  // 'Account', 'Name', 'Balance', 'Key', 'AccType', 'State', 'Price', 'LockedUntil'
+  ATableRow.Account := TAccountComp.AccountNumberToAccountTxtNumber(AItem.account);
+  ATableRow.Name := Variant(AItem.name);
+  ATableRow.Balance := TAccountComp.FormatMoney(AItem.balance);
+ if TWallet.Keys.AccountsKeyList.Find(AItem.accountInfo.accountKey, index) then
+    ATableRow.Key := TDataSourceTool.AccountKeyShortText(TWallet.Keys[index].Name)
+ else
+    ATableRow.Key := TDataSourceTool.AccountKeyShortText(TAccountComp.AccountPublicKeyExport(AItem.accountInfo.accountKey));
+  ATableRow.AccType := Word(AItem.account_type);
+  ATableRow.State := Cardinal(AItem.accountInfo.state);
+  ATableRow.Price := TAccountComp.FormatMoney(Aitem.accountInfo.price);
+  //ATableRow.LockedUntil := Cardinal(AItem.accountInfo.locked_until_block);
+end;
+
+{ TOperationsDataSourceBase }
+
+constructor TOperationsDataSourceBase.Create(AOwner:TComponent);
+var
+  node : TNode;
+begin
+ inherited Create(AOwner);
+ node := TNode.Node;
+  if Assigned(Node) then begin
+    FStart := 0;
+    FEnd := node.Bank.BlocksCount - 1;
+  end else begin
+    FStart := 0;
+    FEnd := 0;
+  end;
+end;
+
+function TOperationsDataSourceBase.GetTimeSpan : TTimeSpan;
+begin
+  Result := TPCOperationsComp.ConvertBlockCountToTimeSpan(FEnd - FStart + 1);
+end;
+
+procedure TOperationsDataSourceBase.SetTimeSpan(const ASpan : TTimeSpan);
+var
+  node : TNode;
+begin
+ node := TNode.Node;
+ if Not Assigned(Node) then exit;
+ FEnd := node.Bank.BlocksCount - 1;
+ FStart := ClipValue(FEnd - (TPCOperationsComp.ConvertTimeSpanToBlockCount(ASpan) + 1), 0, FEnd);
+end;
+
+function TOperationsDataSourceBase.GetItemDisposePolicy : TItemDisposePolicy;
+begin
+  Result := idpNone;
+end;
+
+function TOperationsDataSourceBase.GetColumns : TTableColumns;
+begin
+  Result := TTableColumns.Create('Time', 'Block', 'Account', 'Type', 'Amount', 'Fee', 'Balance', 'Payload', 'OPHASH', 'Description');
+end;
+
+function TOperationsDataSourceBase.GetSearchCapabilities: TSearchCapabilities;
+begin
+  Result := TSearchCapabilities.Create(
+    TSearchCapability.From('Time', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Block', SORTABLE_TEXT_FILTER),
+    TSearchCapability.From('Account', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Type', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Amount', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Fee', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Balance', SORTABLE_NUMERIC_FILTER),
+    TSearchCapability.From('Payload', SORTABLE_TEXT_FILTER),
+    TSearchCapability.From('OPHASH', SORTABLE_TEXT_FILTER),
+    TSearchCapability.From('Description', SORTABLE_TEXT_FILTER)
+  );
+end;
+
+function TOperationsDataSourceBase.GetItemField(constref AItem: TOperationResume; const AColumnName : utf8string) : Variant;
+var
+  index : Integer;
+begin
+   if AColumnName = 'Time' then
+     Result := AItem.Time
+   else if AColumnName = 'Block' then
+     Result := UInt64(AItem.Block) * 4294967296 + UInt32(AItem.NOpInsideBlock)   // number pattern = [block][opindex]
+   else if AColumnName = 'Account' then
+     Result := AItem.AffectedAccount
+   else if AColumnName = 'Type' then
+     Result := AItem.OpSubtype
+   else if AColumnName = 'Amount' then
+     Result := TAccountComp.FormatMoneyDecimal(AItem.Amount)
+   else if AColumnName = 'Fee' then
+     Result := TAccountComp.FormatMoneyDecimal(AItem.Fee)
+   else if AColumnName = 'Balance' then
+     Result := TAccountComp.FormatMoneyDecimal(AItem.Balance)
+   else if AColumnName = 'Payload' then
+     Result := AItem.PrintablePayload
+   else if AColumnName = 'OPHASH' then
+     Result := TPCOperation.FinalOperationHashAsHexa(AItem.OperationHash)
+   else if AColumnName = 'Description' then
+     Result :=  AItem.OperationTxt
+   else raise Exception.Create(Format('Field not found [%s]', [AColumnName]));
+end;
+
+procedure TOperationsDataSourceBase.DehydrateItem(constref AItem: TOperationResume; var ATableRow: Variant);
+var
+  index : Integer;
+  s: ansistring;
+begin
+  // Time
+  ATableRow.Time := UnixTimeToLocalStr(AItem.time);
+
+  // Block
+  if AItem.OpType <> CT_PseudoOp_Reward then
+    ATableRow.Block := Inttostr(AItem.Block) + '/' + Inttostr(AItem.NOpInsideBlock+1)
+  else
+    ATableRow.Block := Inttostr(AItem.Block);
+
+  // Account
+  ATableRow.Account := TAccountComp.AccountNumberToAccountTxtNumber(AItem.AffectedAccount);
+
+  // Type
+  ATableRow.&Type := Variant(TDataSourceTool.OperationShortText(AItem.OpType, AItem.OpSubtype));
+
+  // Amount
+  ATableRow.Amount := TAccountComp.FormatMoney(AItem.Amount);
+  {if opr.Amount>0 then DrawGrid.Canvas.Font.Color := ClGreen
+  else if opr.Amount=0 then DrawGrid.Canvas.Font.Color := clGrayText
+  else DrawGrid.Canvas.Font.Color := clRed;
+  Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfRight,tfVerticalCenter,tfSingleLine]);}
+
+  // Fee
+  ATableRow.Fee := TAccountComp.FormatMoney(AItem.Fee);
+  {  if opr.Fee>0 then DrawGrid.Canvas.Font.Color := ClGreen
+  else if opr.Fee=0 then DrawGrid.Canvas.Font.Color := clGrayText
+  else DrawGrid.Canvas.Font.Color := clRed;}
+
+  // Balance
+  if AItem.time=0 then
+     ATableRow.Balance := '('+TAccountComp.FormatMoney(AItem.Balance)+')'
+  else
+     ATableRow.Balance := TAccountComp.FormatMoney(AItem.Balance);
+  {  if opr.time=0 then begin
+    // Pending operation... showing final balance
+    DrawGrid.Canvas.Font.Color := clBlue;
+    s := '('+TAccountComp.FormatMoney(opr.Balance)+')';
+  end else begin
+    s := TAccountComp.FormatMoney(opr.Balance);
+    if opr.Balance>0 then DrawGrid.Canvas.Font.Color := ClGreen
+    else if opr.Balance=0 then DrawGrid.Canvas.Font.Color := clGrayText
+    else DrawGrid.Canvas.Font.Color := clRed;
+  end;
+  Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfRight,tfVerticalCenter,tfSingleLine]);
+  }
+
+  // Payload
+  ATableRow.Payload := IIF(NOT AnsiString.IsNullOrWhiteSpace(AItem.PrintablePayload), True, False);
+  {    s := opr.PrintablePayload;
+  Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfLeft,tfVerticalCenter,tfSingleLine]); }
+
+  // OPHASH
+  if Length(AItem.OperationHash) > 0 then
+    ATableRow.OPHASH := TDataSourceTool.OperationShortHash( TPCOperation.FinalOperationHashAsHexa(AItem.OperationHash) )
+  else
+    ATableRow.OPHASH := 'None';
+
+  // Description
+  ATableRow.Description := Variant(AItem.OperationTxt);
+
+end;
+
+
+{ TAccountsOperationsDataSource }
+
+constructor TAccountsOperationsDataSource.Create(AOwner:TComponent);
+begin
+  inherited Create(AOwner);
+  FAccounts := TSortedHashSet<Cardinal>.Create;
+end;
+
+destructor TAccountsOperationsDataSource.Destroy;
+begin
+ Inherited;
+ FAccounts.Free;
+end;
+
+function TAccountsOperationsDataSource.GetAccounts : TArray<Cardinal> ;
+begin
+  Result := FAccounts.ToArray;
+end;
+
+procedure TAccountsOperationsDataSource.SetAccounts(const AAccounts : TArray<Cardinal>);
+begin
+  FAccounts.Clear;
+  FAccounts.AddRange(AAccounts);
+end;
+
+procedure TAccountsOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
+var
+  block, i, keyIndex : integer;
+  OPR : TOperationResume;
+  accountBlockOps : TOperationsResumeList;
+  node : TNode;
+  list : Classes.TList;
+  Op : TPCOperation;
+  acc : Cardinal;
+  GC : TScoped;
+begin
+  if FAccounts.Count = 0
+    then exit;
+  node := TNode.Node;
+  if Not Assigned(Node)
+    then exit;
+  accountBlockOps := GC.AddObject(TOperationsResumeList.Create ) as TOperationsResumeList;
+  list := GC.AddObject( Classes.TList.Create ) as Classes.TList;
+  for acc in FAccounts do begin
+    // Load pending operations first
+    list.Clear;
+    accountBlockOps.Clear;
+    Node.Operations.OperationsHashTree.GetOperationsAffectingAccount( acc, list );
+    if list.Count > 0 then
+      for i := list.Count - 1 downto 0 do begin
+        Op := node.Operations.OperationsHashTree.GetOperation( PtrInt( list[i] ) );
+        If TPCOperation.OperationToOperationResume( 0, Op, acc, OPR ) then begin
+          OPR.NOpInsideBlock := i;
+          OPR.Block := Node.Operations.OperationBlock.block; ;
+          OPR.Balance := Node.Operations.SafeBoxTransaction.Account( acc {Op.SignerAccount} ).balance;
+          AContainer.Add(OPR);
+        end;
+    end;
+
+    // Load block ops
+    Node.GetStoredOperationsFromAccount(accountBlockOps, acc, MaxInt, 0, MaxInt);
+    for i := 0 to accountBlockOps.Count - 1 do
+      AContainer.Add(accountBlockOps[i]);
+  end;
+
+end;
+
+{ TPendingOperationsDataSource }
+
+procedure TPendingOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
+var
+  i : integer;
+  node : TNode;
+  Op : TPCOperation;
+  OPR : TOperationResume;
+begin
+ node := TNode.Node;
+  if Not Assigned(Node) then exit;
+  for i := Node.Operations.Count - 1 downto 0 do begin
+    Op := Node.Operations.OperationsHashTree.GetOperation(i);
+    If TPCOperation.OperationToOperationResume(0,Op,Op.SignerAccount,OPR) then begin
+      OPR.NOpInsideBlock := i;
+      OPR.Block := Node.Bank.BlocksCount;
+      OPR.Balance := Node.Operations.SafeBoxTransaction.Account(Op.SignerAccount).balance;
+      AContainer.Add(OPR);
+    end;
+  end;
+end;
+
+
+{ TOperationsDataSource }
+
+procedure TOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
+var
+  block, i, j, keyIndex : integer;
+  OPR : TOperationResume;
+  blockOps : TPCOperationsComp;
+  node : TNode;
+  GC : TScoped;
+
+begin
+  node := TNode.Node;
+  if Not Assigned(Node) then exit;
+  blockOps := GC.AddObject(TPCOperationsComp.Create(Nil)) as TPCOperationsComp;
+  for block := FEnd downto FStart do begin  /// iterate blocks correctly
+    opr := CT_TOperationResume_NUL;
+    if (Node.Bank.Storage.LoadBlockChainBlock(blockOps, block)) then begin
+      AContainer.Add( blockOps.GetMinerRewardPsuedoOperation );
+      if blockOps.Count = 0 then exit;
+      for i := blockOps.Count - 1 downto 0 do begin    // reverse order
+        if TPCOperation.OperationToOperationResume(block, blockOps.Operation[i], blockOps.Operation[i].SignerAccount, opr) then begin
+          opr.NOpInsideBlock := i;
+          opr.Block := block;
+          opr.time := blockOps.OperationBlock.timestamp;
+          AContainer.Add(opr);
+        end;
+      end;
+    end else break;
+  end;
+end;
+
+{ TDataSourceTool }
+
+class function TDataSourceTool.OperationShortHash(const AOpHash : AnsiString) : AnsiString;
+var
+  len : SizeInt;
+begin
+ len := Length(AOpHash);
+  if len > 8 then
+    result := AOpHash.Substring(0, 4) + '...' + AOpHash.Substring(len - 4 - 1, 4)
+  else
+    result := AOpHash;
+end;
+
+class function TDataSourceTool.OperationShortText(const OpType, OpSubType : DWord) : AnsiString;
+begin
+  case OpType of
+    CT_PseudoOp_Reward: case OpSubType of
+      0, CT_PseudoOpSubtype_Miner : result := 'Miner Reward';
+      CT_PseudoOpSubtype_Developer : result := 'Developer Reward';
+      else result := 'Unknown';
+    end;
+    CT_Op_Transaction: case OpSubType of
+      CT_OpSubtype_TransactionSender: Result := 'Send';
+      CT_OpSubtype_TransactionReceiver: Result := 'Receive';
+      CT_OpSubtype_BuyTransactionBuyer: result := 'Buy Account Direct';
+      CT_OpSubtype_BuyTransactionTarget: result := 'Purchased Account Direct';
+      CT_OpSubtype_BuyTransactionSeller: result := 'Sold Account Direct';
+      else result := 'Unknown';
+    end;
+    CT_Op_Changekey: Result := 'Change Key (legacy)';
+    CT_Op_Recover: Result := 'Recover';
+    CT_Op_ListAccountForSale: case OpSubType of
+      CT_OpSubtype_ListAccountForPublicSale: result := 'For Sale';
+      CT_OpSubtype_ListAccountForPrivateSale: result := 'Exclusive Sale';
+      else result := 'Unknown';
+    end;
+    CT_Op_DelistAccount: result := 'Remove Sale';
+    CT_Op_BuyAccount: case OpSubType of
+      CT_OpSubtype_BuyAccountBuyer: result := 'Buy Account';
+      CT_OpSubtype_BuyAccountTarget: result := 'Purchased Account';
+      CT_OpSubtype_BuyAccountSeller: result := 'Sold Account';
+      else result := 'Unknown';
+    end;
+    CT_Op_ChangeKeySigned: result :=  'Change Key';
+    CT_Op_ChangeAccountInfo: result := 'Change Info';
+    else result := 'Unknown';
+  end;
+end;
+
+class function TDataSourceTool.AccountKeyShortText(const AText : AnsiString) : AnsiString;
+begin
+ If Length(AText) > 20 then
+   Result := AText.SubString(0, 17) + '...'
+ else
+   Result := AText;
+end;
+
+end.
+

+ 9 - 8
Units/PascalCoin/UNode.pas

@@ -28,7 +28,7 @@ unit UNode;
 interface
 interface
 
 
 uses
 uses
-  Classes, UBlockChain, UNetProtocol, UAccounts, UCrypto, UThread, SyncObjs, ULog;
+  Classes, UBlockChain, UNetProtocol, UAccounts, UCrypto, UThread, Generics.Collections, SyncObjs, ULog;
 
 
 {$I config.inc}
 {$I config.inc}
 
 
@@ -43,7 +43,7 @@ Type
     FNodeLog : TLog;
     FNodeLog : TLog;
     FLockNodeOperations : TPCCriticalSection;
     FLockNodeOperations : TPCCriticalSection;
     FOperationSequenceLock : TPCCriticalSection;
     FOperationSequenceLock : TPCCriticalSection;
-    FNotifyList : TList;
+    FNotifyList : Classes.TList;
     FBank : TPCBank;
     FBank : TPCBank;
     FOperations : TPCOperationsComp;
     FOperations : TPCOperationsComp;
     FNetServer : TNetServer;
     FNetServer : TNetServer;
@@ -78,7 +78,7 @@ Type
     //
     //
     Procedure NotifyBlocksChanged;
     Procedure NotifyBlocksChanged;
     //
     //
-    procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation : Integer);
+    procedure GetStoredOperationsFromAccount(const OperationsResume: TOperationsResumeList; account_number: Cardinal; MaxDepth, StartOperation, EndOperation : Integer); overload;
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
     Function FindOperation(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : Boolean;
     Function FindOperationExt(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : TSearchOperationResult;
     Function FindOperationExt(Const OperationComp : TPCOperationsComp; Const OperationHash : TRawBytes; var block : Cardinal; var operation_block_index : Integer) : TSearchOperationResult;
     Function FindNOperation(block, account, n_operation : Cardinal; var OpResume : TOperationResume) : TSearchOperationResult;
     Function FindNOperation(block, account, n_operation : Cardinal; var OpResume : TOperationResume) : TSearchOperationResult;
@@ -151,7 +151,7 @@ Type
 
 
 implementation
 implementation
 
 
-Uses UOpTransaction, SysUtils,  UConst, UTime;
+Uses UOpTransaction, SysUtils,  UConst, UTime, UAutoScope, UCommon;
 
 
 var _Node : TNode;
 var _Node : TNode;
 
 
@@ -502,7 +502,7 @@ begin
   FNetServer := TNetServer.Create;
   FNetServer := TNetServer.Create;
   FOperations := TPCOperationsComp.Create(Self);
   FOperations := TPCOperationsComp.Create(Self);
   FOperations.bank := FBank;
   FOperations.bank := FBank;
-  FNotifyList := TList.Create;
+  FNotifyList := Classes.TList.Create;
   {$IFDEF BufferOfFutureOperations}
   {$IFDEF BufferOfFutureOperations}
   FBufferAuxWaitingOperations := TOperationsHashTree.Create;
   FBufferAuxWaitingOperations := TOperationsHashTree.Create;
   {$ENDIF}
   {$ENDIF}
@@ -704,14 +704,14 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
   var opc : TPCOperationsComp;
   var opc : TPCOperationsComp;
     op : TPCOperation;
     op : TPCOperation;
     OPR : TOperationResume;
     OPR : TOperationResume;
-    l : TList;
+    l : Classes.TList;
     i : Integer;
     i : Integer;
     last_block_number, next_block_number : Integer;
     last_block_number, next_block_number : Integer;
   begin
   begin
     if (act_depth<=0) then exit;
     if (act_depth<=0) then exit;
     opc := TPCOperationsComp.Create(Nil);
     opc := TPCOperationsComp.Create(Nil);
     Try
     Try
-      l := TList.Create;
+      l := Classes.TList.Create;
       try
       try
         last_block_number := block_number+1;
         last_block_number := block_number+1;
         while (last_block_number>block_number) And (act_depth>0)
         while (last_block_number>block_number) And (act_depth>0)
@@ -749,12 +749,13 @@ procedure TNode.GetStoredOperationsFromAccount(const OperationsResume: TOperatio
             OPR := CT_TOperationResume_NUL;
             OPR := CT_TOperationResume_NUL;
             OPR.valid := true;
             OPR.valid := true;
             OPR.Block := block_number;
             OPR.Block := block_number;
+            OPR.NOpInsideBlock:=-1;
             OPR.time := opc.OperationBlock.timestamp;
             OPR.time := opc.OperationBlock.timestamp;
             OPR.AffectedAccount := account_number;
             OPR.AffectedAccount := account_number;
             OPR.Amount := opc.OperationBlock.reward;
             OPR.Amount := opc.OperationBlock.reward;
             OPR.Fee := opc.OperationBlock.fee;
             OPR.Fee := opc.OperationBlock.fee;
             OPR.Balance := last_balance;
             OPR.Balance := last_balance;
-            OPR.OperationTxt := 'Blockchain reward';
+            OPR.OperationTxt := 'Miner reward';
             if (nOpsCounter>=StartOperation) And (nOpsCounter<=EndOperation) then begin
             if (nOpsCounter>=StartOperation) And (nOpsCounter<=EndOperation) then begin
               OperationsResume.Add(OPR);
               OperationsResume.Add(OPR);
             end;
             end;

+ 7 - 0
Units/PascalCoin/UTime.pas

@@ -29,6 +29,8 @@ function UnixToUnivDateTime(USec: Longint): TDateTime;
 function UnixTimeToLocalElapsedTime(USec : Longint) : AnsiString;
 function UnixTimeToLocalElapsedTime(USec : Longint) : AnsiString;
 Function DateTimeElapsedTime(dtDate : TDateTime) : AnsiString;
 Function DateTimeElapsedTime(dtDate : TDateTime) : AnsiString;
 
 
+Function UnixTimeToLocalStr(UnixTime : Longint) : AnsiString;
+
 implementation
 implementation
 
 
 Uses DateUtils;
 Uses DateUtils;
@@ -75,4 +77,9 @@ begin
   Result := (Usec / 86400) + UnixStartDate;
   Result := (Usec / 86400) + UnixStartDate;
 end;
 end;
 
 
+Function UnixTimeToLocalStr(UnixTime : Longint) : AnsiString;
+begin
+  Result := DateTimeToStr(UnivDateTime2LocalDateTime(UnixToUnivDateTime(UnixTime)))
+end;
+
 end.
 end.

+ 625 - 0
Units/Utils/UCache.pas

@@ -0,0 +1,625 @@
+unit UCache;
+
+{$ifdef FPC}
+  {$mode Delphi}
+{$endif}
+
+{$modeswitch nestedprocvars}
+
+interface
+
+uses
+  syncobjs, Classes, SysUtils, UCommon, Generics.Collections, LCLIntf;
+
+type
+  TCachedItem<TValue> = class(TInterfacedObject)
+    public
+      FInvalidated : boolean;
+      FFetchedOn : TDateTime;
+      FLastAccessedOn : TDateTime;
+      FAccessedCount : DWord;
+      FSize : DWord;
+      FDisposePolicy : TItemDisposePolicy;
+      FValue : TValue;
+    public
+      constructor Create(const AValue : TValue; ASize: DWord; ADisposePolicy : TItemDisposePolicy);
+      destructor Destroy; override;
+      property Invalidated : boolean read FInvalidated;
+      property FetchedOn : TDateTime read FFetchedOn;
+      property LastAccessedOn : TDateTime read FLastAccessedOn;
+      property AccessedCount : DWord read FAccessedCount;
+      property Size : DWord read FSize;
+      property Value : TValue read FValue;
+  end;
+
+  TCacheBase<TKey, TValue> = class(TComponent)
+    public type
+        TExpirationPolicy = (epSinceFetchedTime, epSinceLastAccessedTime, epNone);
+        TCacheReapPolicy = (crpLeastUsed, crpLargest, crpSmallest, crpOldest, crpIdle, crpASAP, crpNone);
+        TNullValuePolicy = (nvpCacheNormally, nvpReturnButDontCache, nvpThrow);
+    private type
+       __TPair_TKey_TValue = TPair<TKey, TValue>;
+       __TCachedItem_TValue = TCachedItem<TValue>;
+    private
+      FItemFetched: TNotifyManyEventEx; { Args = TKey, TValue }
+      FItemRemoved: TNotifyManyEventEx; { Args = TKey, TValue }
+      function ByExpiredCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function ByAccessedCountCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function ByIdleCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function ByIdleDescendingCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function ByFetchedOnCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function ByAccessedOnCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function BySizeCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function BySizeDescendingCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+      function IsExpiredFilter(const Item : __TPair_TKey_TValue) : boolean;
+    protected type
+      __TDictionary_TKey_TCachedItem_TValue = TDictionary<TKey, __TCachedItem_TValue>;
+    protected
+      FCurrentSize : SizeInt;
+      FMaxCapacity : SizeInt;
+      FItemDisposePolicy : TItemDisposePolicy;
+      FExpirationDuration : TTimeSpan;
+      FExpirationPolicy : TExpirationPolicy;
+      FReapPolicy : TCacheReapPolicy;
+      FNullValuePolicy : TNullValuePolicy;
+      FInternalStorage : __TDictionary_TKey_TCachedItem_TValue;
+      FLock : TSimpleRWSync;
+      function EstimateSize(const AVal: TValue) : SizeInt; virtual; abstract;
+      function Fetch(const AKey: TKey): TValue; virtual; abstract;
+      function IsExpired(ACachedItem: TCachedItem<TValue>) : boolean;
+      procedure MakeSpace(const requestedSpace: SizeInt);
+      procedure MakeSpaceFast(const requestedSpace: SizeInt);
+      procedure OnItemFetched(const AKey: TKey; const AVal: TValue); virtual;
+      procedure OnItemRemoved(const AKey: TKey; const AVal: TValue); virtual;
+      procedure NotifyItemFetched(const AKey: TKey; const AVal: TValue);
+      procedure NotifyItemRemoved(const AKey: TKey; const AVal: TValue);
+      function AddItemInternal(const Key: TKey; const AVal: TValue) : TCachedItem<TValue>;
+      procedure RemoveItemInternal(const AKey: TKey);
+    public
+      constructor Create(AOwner:TComponent; const AItemDisposePolicy : TItemDisposePolicy; const AReapPolicy : TCacheReapPolicy; const AExpirationPolicy : TExpirationPolicy; const ANullValuePolicy : TNullValuePolicy; const AMaxCapacity : Cardinal; const AExpirationDuration : TTimeSpan); overload;
+      destructor Destroy; override;
+      property ItemFetched : TNotifyManyEventEx read FItemFetched;
+      property ItemRemoved : TNotifyManyEventEx read FItemRemoved;
+      property CurrentSize : SizeInt read FCurrentSize;
+      property MaxCapacity : SizeInt read FMaxCapacity;
+      property ExpirationDuration : TTimeSpan read FExpirationDuration;
+      property ExpirationPolicy : TExpirationPolicy read FExpirationPolicy;
+      property ReapPolicy : TCacheReapPolicy read FReapPolicy;
+      property NullValuePolicy : TNullValuePolicy read FNullValuePolicy;
+      function Get(const AKey: TKey) : TCachedItem<TValue>;
+      function ContainsCachedItem(const AKey : TKey) : boolean;
+      procedure BulkLoad(const ABulkValues : TEnumerable<__TPair_TKey_TValue>);
+      procedure Invalidate(const AKey: TKey);
+      procedure Remove(const AKey: TKey);
+      procedure Flush;
+  end;
+
+{ TActionCache }
+
+  TActionCache<TKey, TValue> = class(TCacheBase<TKey, TValue>)
+    public type
+      { IValueFetcher }
+      IValueFetcher = interface
+        function Fetch(const AKey: TKey): TValue;
+      end;
+
+      { ISizeEstimator }
+      ISizeEstimator = interface
+        function Estimate(const AKey: TKey) : SizeInt;
+      end;
+
+      { TNestedFetchFunc }
+      TNestedFetchFunc = function (const AKey : TKey) : TValue is nested;
+
+      { TObjectFetchFunc }
+      TObjectFetchFunc = function (const AKey : TKey) : TValue of object;
+
+      { TGlobalFetchFunc }
+      TGlobalFetchFunc = function (const AKey : TKey) : TValue;
+
+      { TValueFetcher }
+      TValueFetcher = class (TInterfacedObject, IValueFetcher)
+        private
+          FFetcher : IValueFetcher;
+        public
+          function Fetch(const AKey: TKey): TValue;
+          constructor Create(const AFetcher: IValueFetcher);
+          class function FromNestedFunction(const AFunc: TNestedFetchFunc) : IValueFetcher;
+          class function FromObjectFunction(const AFunc: TObjectFetchFunc) : IValueFetcher;
+          class function FromGlobalFunction(const AFunc: TGlobalFetchFunc) : IValueFetcher;
+      end;
+
+      { TNestedEstimateFunc }
+      TNestedEstimateFunc = function (const AKey : TKey) : SizeInt is nested;
+
+      { TObjectEstimateFunc }
+      TObjectEstimateFunc = function (const AKey : TKey) : SizeInt of object;
+
+      { TGlobalEstimateFunc }
+      TGlobalEstimateFunc = function (const AKey : TKey) : SizeInt;
+
+      { TSizeEstimator }
+      TSizeEstimator = class (TInterfacedObject, ISizeEstimator)
+        private
+          FEstimator : ISizeEstimator;
+        public
+          function Estimate(const AKey: TKey) : SizeInt;
+          constructor Create(const AEstimator: ISizeEstimator);
+          class function FromNestedFunction(const AFunc: TNestedEstimateFunc) : ISizeEstimator;
+          class function FromObjectFunction(const AFunc: TObjectEstimateFunc) : ISizeEstimator;
+          class function FromGlobalFunction(const AFunc: TGlobalEstimateFunc) : ISizeEstimator;
+      end;
+
+   private type
+     { GlobalFunctionValueFetcher }
+     GlobalFunctionValueFetcher = class (TInterfacedObject, IValueFetcher)
+        private
+          FFunc : TGlobalFetchFunc;
+        public
+          constructor Create(const AFunc: TGlobalFetchFunc);
+          function Fetch(const AKey: TKey): TValue;
+      end;
+
+      { ObjectFunctionValueFetcher }
+      ObjectFunctionValueFetcher = class (TInterfacedObject, IValueFetcher)
+        private
+          FFunc : TObjectFetchFunc;
+        public
+          constructor Create(const AFunc: TGlobalFetchFunc);
+          function Fetch(const AKey: TKey): TValue;
+      end;
+
+      { NestedFunctionValueFetcher }
+      NestedFunctionValueFetcher = class (TInterfacedObject, IValueFetcher)
+        private
+          FFunc : TNestedFetchFunc;
+        public
+          constructor Create(const AFunc: TNestedFetchFunc);
+          function Fetch(const AKey: TKey): TValue;
+      end;
+
+     { GlobalSizeEstimator }
+     GlobalFunctionEstimator = class (TInterfacedObject, ISizeEstimator)
+        private
+          FFunc : TGlobalEstimateFunc;
+        public
+          constructor Create(const AFunc: TGlobalEstimateFunc);
+          function Estimate(const AKey: TKey): SizeInt;
+      end;
+
+      { ObjectFunctionValueFetcher }
+      ObjectFunctionEstimator = class (TInterfacedObject, ISizeEstimator)
+        private
+          FFunc : TObjectEstimateFunc;
+        public
+          constructor Create(const AFunc: TObjectEstimateFunc);
+          function Estimate(const AKey: TKey): SizeInt;
+      end;
+
+      { NestedFunctionValueFetcher }
+      NestedFunctionEstimator = class (TInterfacedObject, ISizeEstimator)
+        private
+          FFunc : TNestedEstimateFunc;
+        public
+          constructor Create(const TNestedEstimateFunc);
+          function Estimate(const AKey: TKey): SizeInt;
+      end;
+
+    private
+      FValueFetcher : IValueFetcher;
+      FSizeEstimator : ISizeEstimator;
+    protected
+      function EstimateSize(const AVal: TValue) : SizeInt; override;
+      function Fetch(const AKey: TKey): TValue; override;
+    public
+      constructor Create(
+            AOwner:TComponent;
+            const AValueFetcher : IValueFetcher;
+            const ASizeEstimator : ISizeEstimator;
+            const AItemDisposePolicy : TItemDisposePolicy;
+            const AReapPolicy : TCacheReapPolicy;
+            const AExpirationPolicy : TExpirationPolicy;
+            const ANullPolicy : TNullValuePolicy;
+            const AMaxCapacity : Integer;
+            const AExpirationDuration : TTimeSpan); overload;
+  end;
+
+implementation
+
+uses
+  Generics.Defaults, UAutoScope, UCommon.Collections;
+
+
+{ TCachedItem }
+
+constructor TCachedItem<TValue>.Create(const AValue : TValue; ASize: DWord; ADisposePolicy : TItemDisposePolicy);
+begin
+  FInvalidated := false;
+  FFetchedOn := Now;
+  FLastAccessedOn := Now;
+  FFetchedOn := Now;
+  FAccessedCount := 0;
+  FSize := ASize;
+  FDisposePolicy := TItemDisposePolicy;
+  FValue :=  AValue;
+end;
+
+destructor TCachedItem<TValue>.Destroy;
+begin
+  case FDisposePolicy of
+    idpFreeAndNil: FreeAndNil(FValue);
+    idpNil: FValue := nil;
+    idpNone: // do nothing
+  end;
+  inherited;
+end;
+
+{ TCacheBase }
+
+constructor TCacheBase<TKey, TValue>.Create(AOwner:TComponent; const AItemDisposePolicy : TItemDisposePolicy; const AReapPolicy : TCacheReapPolicy; const AExpirationPolicy : TExpirationPolicy; const ANullValuePolicy : TNullValuePolicy; const AMaxCapacity : Cardinal; const AExpirationDuration : TTimeSpan);
+begin
+  inherited Create(AOwner);
+  FItemDisposePolicy := AItemDisposePolicy;
+  FNullValuePolicy := ANullValuePolicy;
+  FInternalStorage := __TDictionary_TKey_TCachedItem_TValue.Create;
+  FCurrentSize := 0;
+  FReapPolicy := AReapPolicy;
+  FExpirationPolicy := AExpirationPolicy;
+  FMaxCapacity := AMaxCapacity;
+  FExpirationDuration := AExpirationDuration;
+  FLock := TSimpleRWSync.Create;
+end;
+
+destructor TCacheBase<TKey, TValue>.Destroy;
+begin
+  Flush;
+  FreeAndNil(FInternalStorage);
+  FreeAndNil(FLock);
+  inherited;
+end;
+
+function TCacheBase<TKey, TValue>.Get(const AKey: TKey) : TCachedItem<TValue>;
+var
+  item: TCachedItem<TValue>;
+begin
+  if NOT FInternalStorage.TryGetValue(AKey, item) then begin
+      try
+        FLock.BeginWrite;
+        if NOT FInternalStorage.TryGetValue(AKey, item) then
+            item := AddItemInternal(AKey, Fetch(AKey));
+      finally
+        FLock.EndWrite;
+      end
+  end else if IsExpired(item) then begin
+    try
+      FLock.BeginWrite;
+      RemoveItemInternal(AKey);
+      item := AddItemInternal(AKey, Fetch(AKey));
+    finally
+      FLock.EndWrite;
+    end
+  end;
+  inc(item.FAccessedCount);
+  item.FLastAccessedOn := Now;
+  if not Assigned(item.Value) then begin
+    case NullValuePolicy of
+      nvpThrow: raise Exception.Create('Cache fetched a null value and this cache NullValuePolicy prohibits null values.');
+      nvpReturnButDontCache: Invalidate(AKey);
+      nvpCacheNormally: ;
+    end
+  end;
+  Result := item;
+end;
+
+function TCacheBase<TKey, TValue>.ContainsCachedItem(const AKey : TKey) : boolean;
+var
+ item : TCachedItem<TValue>;
+begin
+  Result := FInternalStorage.TryGetValue(AKey, item) AND (NOT IsExpired(item));
+end;
+
+function TCacheBase<TKey, TValue>.IsExpired(ACachedItem: TCachedItem<TValue>) : boolean;
+var
+  from : TDateTime;
+begin
+  if ACachedItem.Invalidated then begin
+    Result := true;
+    exit;
+  end;
+
+  case FExpirationPolicy of
+    epSinceFetchedTime: from := ACachedItem.FetchedOn;
+    epSinceLastAccessedTime: from := ACachedItem.LastAccessedOn;
+    else from := Now;
+  end;
+
+  Result := (FExpirationPolicy <> epNone) AND (TTimeSpan.Subtract(Now, from) > FExpirationDuration);
+end;
+
+procedure TCacheBase<TKey, TValue>.Remove(const AKey: TKey);
+begin
+  try
+    FLock.BeginWrite;
+    RemoveItemInternal(AKey);
+  finally
+    FLock.EndWrite;
+  end;
+end;
+
+procedure TCacheBase<TKey, TValue>.Flush;
+var
+  key: TKey;
+begin
+  try
+    FLock.BeginWrite;
+    for key in FInternalStorage.Keys do
+      RemoveItemInternal(FInternalStorage[key]);
+    FInternalStorage.Clear;
+  finally
+    FLock.EndWrite;
+  end
+end;
+
+procedure TCacheBase<TKey, TValue>.BulkLoad(const ABulkValues: TEnumerable<__TPair_TKey_TValue>);
+var
+  kvp: __TPair_TKey_TValue;
+begin
+  try
+    FLock.BeginWrite;
+    for kvp in ABulkValues do begin
+      if FInternalStorage.ContainsKey(kvp.Key) then begin
+        RemoveItemInternal(kvp.Key);
+        AddItemInternal(kvp.Key, kvp.Value);
+      end else AddItemInternal(kvp.Key, kvp.Value);
+    end
+  finally
+    FLock.EndWrite;
+  end
+end;
+
+procedure TCacheBase<TKey, TValue>.Invalidate;
+begin
+  raise Exception.Create('Not implemented');
+end;
+
+
+procedure TCacheBase<TKey, TValue>.OnItemFetched(const AKey : TKey; const AVal: TValue);
+begin
+end;
+
+procedure TCacheBase<TKey, TValue>.OnItemRemoved(const AKey : TKey; const AVal: TValue);
+begin
+end;
+
+procedure TCacheBase<TKey, TValue>.MakeSpace(const requestedSpace: SizeInt);
+type
+  TSortFunc = TOnComparison<__TPair_TKey_TValue>;
+var
+  candidate : __TPair_TKey_TValue;
+  deathRow: TList<__TPair_TKey_TValue>;
+  sortOrder: array of TSortFunc;
+  reapAll: boolean;
+  GC : TScoped;
+begin
+  try
+    FLock.BeginWrite;
+    if requestedSpace > FMaxCapacity then
+      raise Exception.Create(Format('Cache capacity insufficient for requested space [%0:d].',[requestedSpace]));
+
+    // If need space
+    if FReapPolicy = crpASAP then begin
+      MakeSpaceFast(requestedSpace);
+      exit;
+    end;
+
+    // Get elements to be purged (deathrow)
+    deathRow := GC.AddObject(TList<__TPair_TKey_TValue>.Create) as TList<__TPair_TKey_TValue>;
+    deathRow.AddRange(FInternalStorage.ToArray);
+    case FReapPolicy of
+      crpLeastUsed: sortOrder := TArrayTool<TSortFunc>.Create(ByExpiredCompare, ByAccessedCountCompare, BySizeDescendingCompare);
+      crpOldest: sortOrder := TArrayTool<TSortFunc>.Create(ByExpiredCompare, ByFetchedOnCompare);
+      crpIdle: sortOrder := TArrayTool<TSortFunc>.Create(ByExpiredCompare, ByAccessedOnCompare);
+      crpLargest: sortOrder := TArrayTool<TSortFunc>.Create(ByExpiredCompare, BySizeDescendingCompare);
+      crpSmallest: sortOrder := TArrayTool<TSortFunc>.Create(ByExpiredCompare, BySizeCompare);
+      crpNone: begin
+        if FExpirationPolicy <> epNone then begin
+          TListTool<__TPair_TKey_TValue>.Filter(deathRow, TPredicateTool<__TPair_TKey_TValue>.FromFunc(IsExpiredFilter));
+          sortOrder := nil;
+        end else begin
+          deathRow.Clear;
+          sortOrder := nil;
+        end;
+        reapAll := true;
+      end
+      else raise ENotSupportedException(Format('FReapPolicy: [%d]', [FReapPolicy]));
+    end;
+
+    if Length(sortOrder) > 0 then
+      deathRow.Sort(TComparerTool<__TPair_TKey_TValue>.Many(sortOrder));
+
+    // remove items from cache until enough space
+    for candidate in deathRow do
+      if reapAll OR ((MaxCapacity - CurrentSize) < requestedSpace) then
+        RemoveItemInternal(candidate.Key)
+      else break;
+
+    // check enough space was created
+    if (MaxCapacity - CurrentSize) < requestedSpace then
+      raise Exception.Create(Format('Insufficient cache memory (requested space [%d])', [requestedSpace]));
+
+  finally
+    FLock.EndWrite;
+  end
+end;
+
+procedure TCacheBase<TKey, TValue>.MakeSpaceFast(const requestedSpace: SizeInt);
+type
+  THashSet_Key = THashSet<TKey>;
+var
+  savedSpace: SizeInt;
+  deathRow: THashSet_Key;
+  item: __TPair_TKey_TValue;
+  GC : TScoped;
+begin
+  try
+    FLock.BeginWrite;
+    savedSpace := 0;
+    deathRow := GC.AddObject(THashSet<TKey>.Create) as THashSet_Key;
+    for item in FInternalStorage do begin
+      if NOT IsExpired(item.Value) then
+          continue;
+      deathRow.Add(item.Key);
+      inc(savedSpace, item.Value.Size);
+      if requestedSpace >= savedSpace then
+          break;
+    end;
+    if savedSpace < requestedSpace then begin
+      for item in FInternalStorage do begin
+          if (IsExpired(item.Value)) OR (deathRow.Contains(item.Key)) then
+              continue;
+          deathRow.Add(item.Key);
+          inc(savedSpace, item.Value.Size);
+          if requestedSpace >= savedSpace then
+              break;
+      end
+    end;
+    for item in deathRow do
+      RemoveItemInternal(item);
+  finally
+    FLock.EndWrite;
+  end
+end;
+
+function TCacheBase<TKey, TValue>.AddItemInternal(const Key: TKey; const AVal: TValue) : TCachedItem<TValue>;
+var
+  item : TCachedItem<TValue>;
+  size : Cardinal;
+begin
+  item := TCachedItem<TValue>.Create(AVal, EstimateSize(AVal), FItemDisposePolicy);
+
+  if (MaxCapacity - CurrentSize) < item.Size then
+    MakeSpace(item.Size);
+
+  FInternalStorage.Add(key, item);
+  inc(FCurrentSize, item.Size);
+  Result := item;
+end;
+
+procedure TCacheBase<TKey, TValue>.RemoveItemInternal(const AKey: TKey);
+var
+  item : TValue;
+begin
+    item := FInternalStorage[AKey];
+    FInternalStorage.Remove(AKey);
+    dec(FCurrentSize, item.Size);
+    OnItemRemoved(AKey, item);
+    NotifyItemRemoved(AKey, item);
+end;
+
+procedure TCacheBase<TKey, TValue>.NotifyItemFetched(const AKey: TKey; const AVal: TValue);
+begin
+  OnItemFetched(AKey, AVal);
+  ItemFetched.Invoke(Self, [AKey, AVal]);
+end;
+
+procedure TCacheBase<TKey, TValue>.NotifyItemRemoved(const AKey: TKey; const AVal: TValue);
+begin
+  OnItemRemoved(AKey, AVal);
+  ItemRemoved.Invoke(Self, [AKey, AVal]);
+end;
+
+{%region Sort Functions }
+
+function TCacheBase<TKey, TValue>.ByExpiredCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+var
+  leftExp, rightExp : boolean;
+begin
+  leftExp := IsExpired(Left.Value);
+  rightExp := IsExpired(Right.Value);
+  if leftExp <> rightExp then
+    Result := 0
+  else
+    if not leftExp then Result := 1 else Result := -1
+end;
+
+function TCacheBase<TKey, TValue>.ByAccessedCountCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := TCompare.Int32(Left.Value.AccessedCount, Right.Value.AccessedCount);
+end;
+
+function TCacheBase<TKey, TValue>.ByIdleCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := TCompare.Int32(TTimeSpan.Subtract(Now,Left.Value.AccessedOn), TTimeSpan.Subtract(Now,Right.Value.AccessedOn));
+end;
+
+function TCacheBase<TKey, TValue>.ByIdleDescendingCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := -ByIdleCompare(Left, Right);
+end;
+
+function TCacheBase<TKey, TValue>.ByFetchedOnCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := TCompare.Int32(Left.Value.FetchedOn, Right.Value.FetchedOn);
+end;
+
+function TCacheBase<TKey, TValue>.ByAccessedOnCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := TCompare.Int32(Left.Value.AccessedOn, Right.Value.AccessedOn);
+end;
+
+function TCacheBase<TKey, TValue>.BySizeCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := TCompare.Int32(Left.Value.Size, Right.Value.Size);
+end;
+
+function TCacheBase<TKey, TValue>.BySizeDescendingCompare(constref Left, Right: __TPair_TKey_TValue): Integer;
+begin
+  Result := -BySizeCompare(Left, Right);
+end;
+
+function TCacheBase<TKey, TValue>.IsExpiredFilter(const Item : __TPair_TKey_TValue) : boolean;
+begin
+  Result := IsExpired(Item.Value);
+end;
+
+{%endregion}
+
+
+{ ActionCache }
+
+//  const AItemDisposePolicy : TItemDisposePolicy; const AReapPolicy : TCacheReapPolicy; const AExpirationPolicy : TExpirationPolicy; const ANullValuePolicy : TNullValuePolicy; const AMaxCapacity : Cardinal; const AExpirationDuration : TTimeSpan
+constructor TActionCache<TKey, TValue>.Create(
+            AOwner:TComponent;
+            const AValueFetcher : IValueFetcher;
+            const ASizeEstimator : ISizeEstimator;
+            const AItemDisposePolicy : TItemDisposePolicy;
+            const AReapPolicy : TCacheReapPolicy;
+            const AExpirationPolicy : TExpirationPolicy;
+            const ANullPolicy : TNullValuePolicy;
+            const AMaxCapacity : Integer;
+            const AExpirationDuration : TTimeSpan);
+begin
+  Inherited Create(AOwner, AItemDisposePolicy, AReapPolicy, AExpirationPolicy, ANullPolicy, AMaxCapacity, AExpirationDuration);
+  FValueFetcher := AValueFetcher;
+  FSizeEstimator := ASizeEstimator;
+end;
+
+function TActionCache<TKey, TValue>.Fetch(const AKey: TKey): TValue;
+var
+  val : TValue;
+begin
+  val := FValueFetcher.Fetch(AKey);
+  NotifyItemFetched(AKey, val);
+  result := val;
+end;
+
+function TActionCache<TKey, TValue>.EstimateSize(const AVal: TValue) : SizeInt;
+begin
+  if Assigned(FSizeEstimator) then
+    Result := FSizeEstimator.Estimate(AVal)
+  else
+    Result := 0;
+end;
+
+end.
+

+ 689 - 0
Units/Utils/UCommon.Collections.pas

@@ -0,0 +1,689 @@
+{
+  Copyright (c) 2017 - 2018 Sphere 10 Software
+
+  Common tools and extensions for Generics.Collections and collections in general usable
+  across all tiers.
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+    Herman Schoenfeld
+}
+
+unit UCommon.Collections;
+
+{$mode delphi}
+
+{$modeswitch nestedprocvars}
+
+interface
+
+uses
+  Classes, SysUtils, Generics.Collections, Generics.Defaults, UCommon;
+
+type
+
+  { Comparer API }
+
+  // Note: this tries to follow pattern from Generics.Collections for supporting nested/object/global delegates.
+
+  TNestedComparerFunc<T> = function (constref Left, Right: T): Integer is nested;
+
+  TObjectComparerFunc<T> = function (constref Left, Right: T): Integer of object;
+
+  TGlobalComparerFunc<T> = function (constref Left, Right: T): Integer;
+
+  { TComparerTool }
+
+  TComparerTool<T> = class
+    private type
+      __IComparer_T = IComparer<T>;
+    public
+      class function FromFunc(const AFunc: TNestedComparerFunc<T>) : IComparer<T>; overload;
+      class function FromFunc(const AFunc: TObjectComparerFunc<T>) : IComparer<T>; overload;
+      class function FromFunc(const AFunc: TGlobalComparerFunc<T>) : IComparer<T>; overload;
+      class function Many(const comparers: array of TNestedComparerFunc<T>) : IComparer<T>; overload;
+      class function Many(const comparers: array of TObjectComparerFunc<T>) : IComparer<T>; overload;
+      class function Many(const comparers: array of TGlobalComparerFunc<T>) : IComparer<T>; overload;
+      class function Many(const comparers: array of IComparer<T>) : IComparer<T>; overload;
+      class function Many(const comparers: TEnumerable<__IComparer_T>) : IComparer<T>; overload;
+      class function AlwaysEqual : IComparer<T>;
+    private
+      // These should be nested but FPC doesn't support nested functions in generics
+      class function AlwaysEqualHandler(constref Left, Right: T) : Integer;
+  end;
+
+  { Predicate API }
+
+  // Note: the pattern for nested/object/global delegates is custom
+
+  TNestedPredicateFunc<T> = function (constref AVal : T) : boolean is nested;
+
+  TObjectPredicateFunc<T> = function (constref AVal : T) : boolean of object;
+
+  TGlobalPredicateFunc<T> = function (constref AVal : T) : boolean;
+
+  IPredicate<T> = interface
+    function Evaluate (constref AValue: T) : boolean;
+  end;
+
+  TPredicateTool<T> = class
+    private type
+      __IPredicate_T = IPredicate<T>;
+    public
+      class function FromFunc(const AFunc: TNestedPredicateFunc<T>) : IPredicate<T>; overload;
+      class function FromFunc(const AFunc: TObjectPredicateFunc<T>) : IPredicate<T>; overload;
+      class function FromFunc(const AFunc: TGlobalPredicateFunc<T>) : IPredicate<T>; overload;
+      class function AndMany(const APredicates : array of IPredicate<T>) : IPredicate<T>; overload;
+      class function AndMany(const APredicates : array of TNestedPredicateFunc<T>) : IPredicate<T>; overload;
+      class function AndMany(const APredicates : array of TObjectPredicateFunc<T>) : IPredicate<T>; overload;
+      class function AndMany(const APredicates : array of TGlobalPredicateFunc<T>) : IPredicate<T>; overload;
+      class function OrMany(const APredicates : array of IPredicate<T>) : IPredicate<T>; overload;
+      class function OrMany(const APredicates : array of TNestedPredicateFunc<T>) : IPredicate<T>; overload;
+      class function OrMany(const APredicates : array of TObjectPredicateFunc<T>) : IPredicate<T>; overload;
+      class function OrMany(const APredicates : array of TGlobalPredicateFunc<T>) : IPredicate<T>; overload;
+      class function TruePredicate : IPredicate<T>;
+      class function FalsePredicate : IPredicate<T>;
+      class function NegatePredicate(const APredicate : IPredicate<T>) : IPredicate<T>;
+    private
+      // These should be nested but FPC doesn't support nested functions in generics
+      class function TrueHandler(constref AItem: T) : boolean;
+      class function FalseHandler(constref AItem: T) : boolean;
+  end;
+
+  { TListTool }
+
+  TListTool<T> = class
+    class function Copy(const AList: TList<T>; const AIndex, ACount : SizeInt) : TList<T>;
+    class function Range(const AList: TList<T>; const AIndex, ACount : SizeInt) : SizeInt;
+    class function Skip(const AList: TList<T>; const ACount : SizeInt) : SizeInt;
+    class function Take(const AList: TList<T>; const ACount : SizeInt) : SizeInt;
+    class function RemoveBy(const AList: TList<T>; const APredicate: IPredicate<T>) : SizeInt; overload;
+    class function RemoveBy(const AList: TList<T>; const APredicate: IPredicate<T>; const ADisposePolicy : TItemDisposePolicy) : SizeInt; overload;
+    class function FilterBy(const AList: TList<T>; const APredicate: IPredicate<T>) : SizeInt; overload;
+    class function FilterBy(const AList: TList<T>; const APredicate: IPredicate<T>; const ADisposePolicy : TItemDisposePolicy) : SizeInt; overload;
+    class procedure DiposeItem(const AList: TList<T>; const index : SizeInt; const ADisposePolicy : TItemDisposePolicy);
+  end;
+
+  { Private types (implementation only) - FPC Bug 'Global Generic template references static symtable' }
+
+  { TNestedComparer }
+
+  TNestedComparer<T> = class(TInterfacedObject, IComparer<T>)
+   private
+     FFunc: TNestedComparerFunc<T>;
+   public
+     constructor Create(const AComparerFunc: TNestedComparerFunc<T>); overload;
+     function Compare(constref Left, Right: T): Integer;
+  end;
+
+  { TObjectComparer }
+
+  TObjectComparer<T> = class(TInterfacedObject, IComparer<T>)
+   private
+     FFunc: TObjectComparerFunc<T>;
+   public
+     constructor Create(const AComparerFunc: TObjectComparerFunc<T>); overload;
+     function Compare(constref Left, Right: T): Integer;
+  end;
+
+  { TGlobalComparer }
+
+  TGlobalComparer<T> = class(TInterfacedObject, IComparer<T>)
+   private
+     FFunc: TGlobalComparerFunc<T>;
+   public
+     constructor Create(const AComparerFunc: TGlobalComparerFunc<T>); overload;
+     function Compare(constref Left, Right: T): Integer;
+  end;
+
+  { TManyComparer }
+
+  TManyComparer<T> = class(TInterfacedObject, IComparer<T>)
+     private type
+       IComparer_T = IComparer<T>;
+     private
+       FComparers : TArray<IComparer_T>;
+     public
+       constructor Create(const comparers: TArray<IComparer_T>); overload;
+       function Compare(constref Left, Right: T): Integer;
+   end;
+
+  { TNestedPredicate }
+
+  TNestedPredicate<T> = class (TInterfacedObject, IPredicate<T>)
+   private
+     FFunc : TNestedPredicateFunc<T>;
+   public
+     constructor Create(const AFunc: TNestedPredicateFunc<T>); overload;
+     function Evaluate (constref AValue: T) : boolean;
+  end;
+
+  { TObjectPredicate }
+
+  TObjectPredicate<T> = class (TInterfacedObject, IPredicate<T>)
+   private
+     FFunc : TObjectPredicateFunc<T>;
+   public
+     constructor Create(const AFunc: TObjectPredicateFunc<T>); overload;
+     function Evaluate (constref AValue: T) : boolean;
+  end;
+
+  { TGlobalPredicate }
+
+  TGlobalPredicate<T> = class (TInterfacedObject, IPredicate<T>)
+   private
+     FFunc : TGlobalPredicateFunc<T>;
+   public
+     constructor Create(const AFunc: TGlobalPredicateFunc<T>); overload;
+     function Evaluate (constref AValue: T) : boolean;
+  end;
+
+  { TNotPredicate }
+
+  TNotPredicate<T> = class (TInterfacedObject, IPredicate<T>)
+    private
+      FPredicate : IPredicate<T>;
+    public
+      constructor Create(const APredicate : IPredicate<T>); overload;
+      function Evaluate (constref AValue: T) : boolean;
+  end;
+
+  { TAndManyPredicate }
+
+  TAndManyPredicate<T> = class (TInterfacedObject, IPredicate<T>)
+   private type
+     __IPredicate_T = IPredicate<T>;
+     __TArrayTool = TArrayTool<__IPredicate_T>;
+   private
+     FPredicates : TArray<__IPredicate_T>;
+   public
+     constructor Create(const APredicates: TArray<__IPredicate_T>); overload;
+     function Evaluate (constref AValue: T) : boolean;
+  end;
+
+  { TOrManyPredicate }
+
+  TOrManyPredicate<T> =  class (TInterfacedObject, IPredicate<T>)
+   private type
+     __IPredicate_T = IPredicate<T>;
+     __TArrayTool = TArrayTool<__IPredicate_T>;
+   private
+     FPredicates : TArray<__IPredicate_T>;
+   public
+     constructor Create(const APredicates: TArray<__IPredicate_T>); overload;
+     function Evaluate (constref AValue: T) : boolean;
+  end;
+
+
+implementation
+
+{%region Comparer API}
+
+class function TComparerTool<T>.FromFunc(const AFunc: TNestedComparerFunc<T>) : IComparer<T>;
+begin
+  Result := TNestedComparer<T>.Create(AFunc);
+end;
+
+class function TComparerTool<T>.FromFunc(const AFunc: TObjectComparerFunc<T>) : IComparer<T>;
+begin
+  Result := TObjectComparer<T>.Create(AFunc);
+end;
+
+class function TComparerTool<T>.FromFunc(const AFunc: TGlobalComparerFunc<T>) : IComparer<T>;
+begin
+  Result := TGlobalComparer<T>.Create(AFunc);
+end;
+
+class function TComparerTool<T>.Many(const comparers: array of TNestedComparerFunc<T>) : IComparer<T>;
+var
+  i : Integer;
+  internalComparers : TArray<__IComparer_T>;
+begin
+  SetLength(internalComparers, Length(comparers));
+  for i := 0 to High(comparers) do
+    internalComparers[i] := TNestedComparer<T>.Create(comparers[i]);
+  Result := TManyComparer<T>.Create(internalComparers);
+end;
+
+class function TComparerTool<T>.Many(const comparers: array of TObjectComparerFunc<T>) : IComparer<T>;
+var
+  i : Integer;
+  internalComparers : TArray<__IComparer_T>;
+begin
+  SetLength(internalComparers, Length(comparers));
+  for i := 0 to High(comparers) do
+    internalComparers[i] := TObjectComparer<T>.Create(comparers[i]);
+  Result := TManyComparer<T>.Create(internalComparers);
+end;
+
+class function TComparerTool<T>.Many(const comparers: array of TGlobalComparerFunc<T>) : IComparer<T>;
+var
+  i : Integer;
+  internalComparers : TArray<__IComparer_T>;
+begin
+  SetLength(internalComparers, Length(comparers));
+  for i := 0 to High(comparers) do
+    internalComparers[i] := TGlobalComparer<T>.Create(comparers[i]);
+  Result := TManyComparer<T>.Create(internalComparers);
+end;
+
+class function TComparerTool<T>.Many(const comparers: array of IComparer<T>) : IComparer<T>;
+type
+  __TArrayTool_IComparer_T = TArrayTool<__IComparer_T>;
+begin
+  Result := TManyComparer<T>.Create( __TArrayTool_IComparer_T.Copy(comparers) );
+end;
+
+class function TComparerTool<T>.Many(const comparers: TEnumerable<__IComparer_T>) : IComparer<T>;
+var
+  i : integer;
+  comparer : __IComparer_T;
+  internalComparers : TArray<__IComparer_T>;
+begin
+  for comparer in comparers do begin
+    SetLength(internalComparers, Length(internalComparers) + 1);
+    internalComparers[High(internalComparers)] := comparer;
+  end;
+  Result := TManyComparer<T>.Create(internalComparers);
+end;
+
+class function TComparerTool<T>.AlwaysEqual : IComparer<T>;
+type
+  __TGlobalComparerFunc_T = TGlobalComparerFunc<T>;
+begin
+  Result :=  TComparerTool<T>.FromFunc( AlwaysEqualHandler );
+end;
+
+class function TComparerTool<T>.AlwaysEqualHandler(constref Left, Right: T) : Integer;
+begin
+  Result := 0;
+end;
+
+{ TNestedComparer }
+
+constructor TNestedComparer<T>.Create(const AComparerFunc: TNestedComparerFunc<T>);
+begin
+  FFunc := AComparerFunc;
+end;
+
+function TNestedComparer<T>.Compare(constref Left, Right: T): Integer;
+begin
+  Result := FFunc(Left, Right);
+end;
+
+{ TObjectComparer }
+
+constructor TObjectComparer<T>.Create(const AComparerFunc: TObjectComparerFunc<T>);
+begin
+  FFunc := AComparerFunc;
+end;
+
+function TObjectComparer<T>.Compare(constref Left, Right: T): Integer;
+begin
+  Result := FFunc(Left, Right);
+end;
+
+{ TGlobalComparer }
+
+constructor TGlobalComparer<T>.Create(const AComparerFunc: TGlobalComparerFunc<T>);
+begin
+  FFunc := AComparerFunc;
+end;
+
+function TGlobalComparer<T>.Compare(constref Left, Right: T): Integer;
+begin
+  Result := FFunc(Left, Right);
+end;
+
+{ TManyComparer }
+
+constructor TManyComparer<T>.Create(const comparers: TArray<IComparer_T>);
+begin
+  FComparers := comparers;
+end;
+
+function TManyComparer<T>.Compare(constref Left, Right: T): Integer;
+var
+  i : Integer;
+begin
+  if Length(FComparers) = 0 then
+    raise Exception.Create('No comparers defined');
+  for i := Low(FComparers) to High(FComparers) do begin
+    Result := FComparers[i].Compare(Left, Right);
+    if (Result <> 0) or (i = High(FComparers)) then exit;
+  end;
+end;
+
+
+{%endegion}
+
+{%region Predicate API}
+
+{ TPredicateTool }
+
+class function TPredicateTool<T>.FromFunc(const AFunc: TNestedPredicateFunc<T>) : IPredicate<T>;
+begin
+  Result := TNestedPredicate<T>.Create(AFunc);
+end;
+
+class function TPredicateTool<T>.FromFunc(const AFunc: TObjectPredicateFunc<T>) : IPredicate<T>;
+begin
+  Result := TObjectPredicate<T>.Create(AFunc);
+end;
+
+class function TPredicateTool<T>.FromFunc(const AFunc: TGlobalPredicateFunc<T>) : IPredicate<T>;
+begin
+  Result := TGlobalPredicate<T>.Create(AFunc);
+end;
+
+class function TPredicateTool<T>.AndMany(const APredicates : array of IPredicate<T>) : IPredicate<T>;
+type
+  __TArrayTool_IPredicate_T = TArrayTool<__IPredicate_T>;
+var
+  arr : TArray<__IPredicate_T>;
+  i : Integer;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := 0 to High(APredicates) do
+    arr[i] := APredicates[i];
+  //arr := __TArrayTool_IPredicate_T.Copy( APredicates);  // TODO: fix ArrayTool.Copy
+  Result := TAndManyPredicate<T>.Create( arr );
+end;
+
+class function TPredicateTool<T>.AndMany(const APredicates : array of TNestedPredicateFunc<T>) : IPredicate<T>;
+var
+  i : integer;
+  arr : TArray<__IPredicate_T>;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := Low(APredicates) to High(APredicates) do
+    arr[i - Low(APredicates)] := TPredicateTool<T>.FromFunc(APredicates[i]);
+  Result := AndMany(arr);
+end;
+
+class function TPredicateTool<T>.AndMany(const APredicates : array of TObjectPredicateFunc<T>) : IPredicate<T>;
+var
+  i : integer;
+  arr : TArray<__IPredicate_T>;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := Low(APredicates) to High(APredicates) do
+    arr[i - Low(APredicates)] := TPredicateTool<T>.FromFunc(APredicates[i]);
+  Result := AndMany(arr);
+end;
+
+class function TPredicateTool<T>.AndMany(const APredicates : array of TGlobalPredicateFunc<T>) : IPredicate<T>;
+var
+  i : integer;
+  arr : TArray<__IPredicate_T>;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := Low(APredicates) to High(APredicates) do
+    arr[i - Low(APredicates)] := TPredicateTool<T>.FromFunc(APredicates[i]);
+  Result := AndMany(arr);
+end;
+
+class function TPredicateTool<T>.OrMany(const APredicates : array of IPredicate<T>) : IPredicate<T>;
+type
+  __TArrayTool_IPredicate_T = TArrayTool<__IPredicate_T>;
+begin
+  Result := TOrManyPredicate<T>.Create( __TArrayTool_IPredicate_T.Copy( APredicates) );
+end;
+
+class function TPredicateTool<T>.OrMany(const APredicates : array of TNestedPredicateFunc<T>) : IPredicate<T>;
+var
+  i : integer;
+  arr : TArray<__IPredicate_T>;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := Low(APredicates) to High(APredicates) do
+    arr[i - Low(APredicates)] := TPredicateTool<T>.FromFunc(APredicates[i]);
+  Result := OrMany(arr);
+end;
+
+class function TPredicateTool<T>.OrMany(const APredicates : array of TObjectPredicateFunc<T>) : IPredicate<T>;
+var
+  i : integer;
+  arr : TArray<__IPredicate_T>;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := Low(APredicates) to High(APredicates) do
+    arr[i - Low(APredicates)] := TPredicateTool<T>.FromFunc(APredicates[i]);
+  Result := OrMany(arr);
+end;
+
+class function TPredicateTool<T>.OrMany(const APredicates : array of TGlobalPredicateFunc<T>) : IPredicate<T>;
+var
+  i : integer;
+  arr : TArray<__IPredicate_T>;
+begin
+  SetLength(arr, Length(APredicates));
+  for i := Low(APredicates) to High(APredicates) do
+    arr[i - Low(APredicates)] := TPredicateTool<T>.FromFunc(APredicates[i]);
+  Result := OrMany(arr);
+end;
+
+class function TPredicateTool<T>.TruePredicate : IPredicate<T>;
+begin
+  Result := TPredicateTool<T>.FromFunc(TrueHandler);
+end;
+
+class function TPredicateTool<T>.FalsePredicate : IPredicate<T>;
+begin
+  Result := TPredicateTool<T>.FromFunc(FalseHandler);
+end;
+
+class function TPredicateTool<T>.NegatePredicate(const APredicate : IPredicate<T>) : IPredicate<T>;
+begin
+  Result := TNotPredicate<T>.Create(APredicate);
+end;
+
+// Shold be nested funcion but generics can't have in FPC!
+class function TPredicateTool<T>.TrueHandler(constref AItem: T) : boolean;
+begin
+  Result := true;
+end;
+
+// Shold be nested funcion but generics can't have in FPC!
+class function TPredicateTool<T>.FalseHandler(constref AItem: T) : boolean;
+begin
+  Result := true;
+end;
+
+{ TNestedPredicate }
+
+constructor TNestedPredicate<T>.Create(const AFunc: TNestedPredicateFunc<T>);
+begin
+  FFunc := AFunc;
+end;
+
+function TNestedPredicate<T>.Evaluate (constref AValue: T) : boolean;
+begin
+  Result := FFunc(AValue);
+end;
+
+{ TObjectPredicate }
+
+constructor TObjectPredicate<T>.Create(const AFunc: TObjectPredicateFunc<T>);
+begin
+  FFunc := AFunc;
+end;
+
+function TObjectPredicate<T>.Evaluate (constref AValue: T) : boolean;
+begin
+  Result := FFunc(AValue);
+end;
+
+{ TGlobalPredicate }
+
+constructor TGlobalPredicate<T>.Create(const AFunc: TGlobalPredicateFunc<T>);
+begin
+  FFunc := AFunc;
+end;
+
+function TGlobalPredicate<T>.Evaluate (constref AValue: T) : boolean;
+begin
+  Result := FFunc(AValue);
+end;
+
+{ TNotPredicate }
+
+constructor TNotPredicate<T>.Create(const APredicate: IPredicate<T>);
+begin
+  FPredicate := APredicate;
+end;
+
+function TNotPredicate<T>.Evaluate (constref AValue: T) : boolean;
+begin
+  Result := NOT FPredicate.Evaluate(AValue);
+end;
+
+{ TAndManyPredicate }
+
+constructor TAndManyPredicate<T>.Create(const APredicates: TArray<__IPredicate_T>);
+begin
+  if Length(APredicates) < 2 then
+    raise EArgumentException.Create('APredicates Must contain at least 2 predicates');
+
+  FPredicates := APredicates;
+end;
+
+function TAndManyPredicate<T>.Evaluate (constref AValue: T) : boolean;
+var
+  i : integer;
+begin
+  Result := FPredicates[0].Evaluate(AValue);
+  for i := 1 to High(FPredicates) do begin
+    if NOT Result then
+      exit;
+    Result := Result AND FPredicates[i].Evaluate(AValue);
+  end;
+end;
+
+{ TOrManyPredicate }
+
+constructor TOrManyPredicate<T>.Create(const APredicates: TArray<__IPredicate_T>);
+begin
+  if Length(APredicates) < 2 then
+    raise EArgumentException.Create('APredicates Must contain at least 2 predicates');
+
+  FPredicates := APredicates;
+end;
+
+function TOrManyPredicate<T>.Evaluate (constref AValue: T) : boolean;
+var
+  i : integer;
+begin
+  Result := FPredicates[0].Evaluate(AValue);
+  for i := 1 to High(FPredicates) do begin
+    if Result then
+      exit;
+    Result := Result OR FPredicates[i].Evaluate(AValue);
+  end;
+end;
+
+{%endregion}
+
+{%region TListTool}
+
+class function TListTool<T>.Copy(const AList: TList<T>; const AIndex, ACount : SizeInt) : TList<T>;
+var
+  i : Integer;
+begin
+  Result := TList<T>.Create;
+
+  for i := 0 to ACount do
+      Result.Add(AList[AIndex + i]);
+
+end;
+
+class function TListTool<T>.Range(const AList: TList<T>; const AIndex, ACount : SizeInt) : SizeInt;
+var
+  from, to_, listCount : SizeInt;
+begin
+
+  listCount := AList.Count;
+
+  from := ClipValue(AIndex, 0, listCount - 1);
+  to_ := ClipValue(AIndex + ACount, 0, listCount - 1);
+
+  if to_ <= from then begin
+    Result := 0;
+    exit;
+  end;
+
+  if from > 0 then
+    AList.DeleteRange(0, from);
+
+  if to_ < (listCount - 1) then
+    AList.DeleteRange(to_ - from, AList.Count);
+
+  Result := AList.Count - listCount;
+end;
+
+class function TListTool<T>.Skip(const AList: TList<T>; const ACount : SizeInt) : SizeInt;
+begin
+  Result := Range(AList, 0 + ACount, AList.Count - ACount);
+end;
+
+class function TListTool<T>.Take(const AList: TList<T>; const ACount : SizeInt) : SizeInt;
+begin
+  Result := Range(AList, 0, ACount);
+end;
+
+class function TListTool<T>.RemoveBy(const AList: TList<T>; const APredicate: IPredicate<T>) : SizeInt;
+begin
+  Result := RemoveBy(AList, APredicate, idpNone);
+end;
+
+class function TListTool<T>.RemoveBy(const AList: TList<T>; const APredicate: IPredicate<T>; const ADisposePolicy : TItemDisposePolicy) : SizeInt;
+var
+  i : SizeInt;
+  item : T;
+begin
+  Result := 0;
+  i := AList.Count-1;
+  while i >= 0 do begin
+    item := AList[i];
+    if APredicate.Evaluate(item) then begin
+      DiposeItem(AList, i, ADisposePolicy);
+      AList.Delete(i);
+      inc(Result);
+    end;
+    Dec(i);
+  end;
+end;
+
+class function TListTool<T>.FilterBy(const AList: TList<T>; const APredicate: IPredicate<T>) : SizeInt;
+begin
+  Result := FilterBy(AList, APredicate, idpNone);
+end;
+
+class function TListTool<T>.FilterBy(const AList: TList<T>; const APredicate: IPredicate<T>; const ADisposePolicy : TItemDisposePolicy) : SizeInt;
+begin
+  Result := RemoveBy(AList, TPredicateTool<T>.NegatePredicate ( APredicate ) );
+end;
+
+class procedure TListTool<T>.DiposeItem(const AList: TList<T>; const index : SizeInt; const ADisposePolicy : TItemDisposePolicy);
+var
+  item : T;
+begin
+  item := AList[index];
+  case ADisposePolicy of
+    idpNone: ;
+    idpNil: AList[index] := default(T);
+    idpFreeAndNil: begin
+      item := AList[index];
+      FreeAndNil(item);
+      AList[index] := default(T);
+    end
+    else raise ENotSupportedException(Format('TItemDisposePolicy: [%d]', [Ord(ADisposePolicy)]));
+  end;
+end;
+
+{%endregion}
+
+end.
+

+ 1035 - 0
Units/Utils/UCommon.Data.pas

@@ -0,0 +1,1035 @@
+{
+  Copyright (c) 2017 - 2018 Sphere 10 Software
+
+  Common data-oriented classes usable across all tiers.
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+    Herman Schoenfeld
+    Maciej Izak (hnb)
+}
+
+unit UCommon.Data;
+
+{$IFDEF FPC}
+  {$MODE Delphi}
+{$ENDIF}
+
+{$MODESWITCH NESTEDPROCVARS}
+
+interface
+
+uses
+  Classes, SysUtils, Generics.Collections, UCommon, UCommon.Collections, Generics.Defaults,
+  Variants, LazUTF8, math, typinfo, syncobjs;
+
+type
+  TSortDirection = (sdNone, sdAscending, sdDescending);
+  TFilterOperand = (foAnd, foOr);
+  TSortNullPolicy = (snpNone, snpNullFirst, snpNullLast);
+  TVisualGridFilter = (vgfMatchTextExact, vgfMatchTextBeginning, vgfMatchTextEnd,
+    vgfMatchTextAnywhere, vgfNumericEQ, vgfNumericLT, vgfNumericLTE, vgfNumericGT,
+    vgfNumericGTE, vgfNumericBetweenInclusive, vgfNumericBetweenExclusive, vgfSortable);
+  TVisualGridFilters = set of TVisualGridFilter;
+
+const
+  TEXT_FILTER = [vgfMatchTextExact, vgfMatchTextBeginning, vgfMatchTextEnd, vgfMatchTextAnywhere];
+  NUMERIC_FILTER = [vgfNumericEQ, vgfNumericLT, vgfNumericLTE, vgfNumericGT, vgfNumericGTE, vgfNumericBetweenInclusive, vgfNumericBetweenExclusive];
+  SORTABLE_TEXT_FILTER = TEXT_FILTER + [vgfSortable];
+  SORTABLE_NUMERIC_FILTER = NUMERIC_FILTER + [vgfSortable];
+  SORTABLE_FILTER = [vgfSortable];
+  NON_SORTABLE_FILTER = [vgfMatchTextExact, vgfMatchTextBeginning, vgfMatchTextEnd,
+    vgfMatchTextAnywhere, vgfNumericEQ, vgfNumericLT, vgfNumericLTE, vgfNumericGT,
+    vgfNumericGTE, vgfNumericBetweenInclusive, vgfNumericBetweenExclusive];
+
+
+type
+
+  { TTableColumn }
+
+  //TODO: this structure is not being used
+  TTableColumn = record
+    Name : utf8string;
+    Binding : AnsiString;
+    class function From(AName: AnsiString) : TTableColumn; overload; static;
+    class function From(AName: utf8string; ABinding: AnsiString) : TTableColumn; overload; static;
+  end;
+
+  TTableColumns = TArray<utf8string>;  //TODO: Maciej, should this be array of TTableColumn or just change header title in gui?
+  PTableColumns = ^TTableColumns;
+  ETableRow = class(Exception);
+
+  { TTableRow }
+
+  TTableRow = class(TInvokeableVariantType)
+  private
+    class constructor Create;
+    class destructor Destroy;
+  protected type
+    TColumnMapToIndex = TDictionary<utf8string, Integer>;
+    TColumnsDictionary = TObjectDictionary<PTableColumns, TColumnMapToIndex>;
+  protected class var
+    FColumns: TColumnsDictionary;
+  protected
+    class function MapColumns(AColumns: PTableColumns): TColumnMapToIndex;
+  public
+    function GetProperty(var Dest: TVarData; const V: TVarData; const Name: string): Boolean; override;
+    function SetProperty(var V: TVarData; const Name: string; const Value: TVarData): Boolean; override;
+    procedure Clear(var V: TVarData); override;
+    procedure Copy(var Dest: TVarData; const Source: TVarData; const Indirect: Boolean); override;
+    function DoFunction(var Dest: TVarData; const V: TVarData; const Name: string; const Arguments: TVarDataArray): Boolean; override;
+    class function New(AColumns: PTableColumns): Variant;
+  end;
+
+  TTableRowData = packed record
+  public
+    vtype: tvartype;
+  private
+    vfiller1 : word;
+    vfiller2: int32;
+  public
+    vcolumnmap: TTableRow.TColumnMapToIndex;
+    vvalues: TArray<Variant>;
+  end;
+
+  { TColumnFilter }
+
+  TColumnFilter = record
+    ColumnName: utf8string;
+    Sort: TSortDirection;
+    Filter: TVisualGridFilter;
+    Values: array of Variant;
+  end;
+
+  { TFilterCriteria }
+
+  TFilterCriteria = TArray<TColumnFilter>;
+
+  { TApplyFilterDelegate }
+
+  TApplyFilterDelegate<T> = function (constref AItem : T; constref AColumnFilter: TColumnFilter) : boolean of object;
+
+  { TApplySortDelegate }
+
+  TApplySortDelegate<T> = function (constref Left, Right : T; constref AColumnFilter: TColumnFilter) : Integer of object;
+
+  { TDataTable }
+
+  PDataTable = ^TDataTable;
+  TDataTable = record
+  public
+    Columns: TTableColumns;
+    Rows : TArray<Variant>;
+  end;
+
+
+  { TColumnFilterPredicate -- should be implementation only }
+
+  TColumnFilterPredicate<T> = class (TInterfacedObject, IPredicate<T>)
+     private
+       FFilter : TColumnFilter;
+       FDelegate : TApplyFilterDelegate<T>;
+     public
+       constructor Create(const AFilter : TColumnFilter; const ADelegate : TApplyFilterDelegate<T>); overload;
+       function Evaluate (constref AValue: T) : boolean;
+   end;
+
+  {TColumnFieldComparer }
+
+  TColumnFieldComparer<T> = class (TInterfacedObject, IComparer<T>)
+    private
+      FFilter : TColumnFilter;
+      FDelegate: TApplySortDelegate<T>;
+    public
+      constructor Create(const AFilter : TColumnFilter; const ADelegate: TApplySortDelegate<T>);
+      function Compare(constref ALeft, ARight: T): Integer; virtual;
+  end;
+
+  { TPageFetchParams }
+
+  TPageFetchParams = record
+    PageIndex: Integer;
+    PageSize: Integer;
+    Filter: TFilterCriteria;
+    Operand : TFilterOperand;
+    constructor Create(AIndex: Integer; ASize: Integer; AFilter: TFilterCriteria; AOperand: TFilterOperand);
+    function GetSortFilters : TArray<TColumnFilter>;
+    function GetSearchFilters : TArray<TColumnFilter>;
+  end;
+
+  { TPageFetchResult }
+
+  TPageFetchResult = record
+    PageIndex: Integer;
+    PageCount: Integer;
+    TotalDataCount: Integer;
+  end;
+
+  { TSearchCapability }
+
+  PSearchCapability = ^TSearchCapability;
+  TSearchCapability = record
+    ColumnName : utf8string;
+    SupportedFilters : TVisualGridFilters;
+    class function From(const AName : utf8string; AFilterType : TVisualGridFilters) : TSearchCapability; static;
+  end;
+
+  { TSearchCapabilities }
+
+  TSearchCapabilities = array of TSearchCapability;
+
+  { IDataSource }
+
+  IDataSource = interface
+    function FetchPage(constref AParams: TPageFetchParams; var ADataTable: TDataTable): TPageFetchResult;
+    function GetSearchCapabilities: TSearchCapabilities;
+    property SearchCapabilities : TSearchCapabilities read GetSearchCapabilities;
+  end;
+
+  { TCustomDataSource }
+// TODO: split  into TBindedDataSource <- TCustomDataSource
+  TCustomDataSource<T> = class(TComponent, IDataSource)
+    private
+      FLock : TCriticalSection;
+      FColumns : TTableColumns;
+    protected
+      function GetNullPolicy(const AFilter : TColumnFilter) : TSortNullPolicy; virtual;
+      function GetItemDisposePolicy : TItemDisposePolicy; virtual; abstract;
+      function GetColumns : TTableColumns; virtual; abstract;
+      function ApplyColumnSort(constref Left, Right : T; constref AFilter: TColumnFilter) : Integer; virtual;
+      function ApplyColumnFilter(constref AItem: T; constref AFilter: TColumnFilter) : boolean; virtual;
+      function GetItemField(constref AItem: T; const AColumnName : utf8string) : Variant; virtual; abstract;
+      procedure DehydrateItem(constref AItem: T; var ATableRow: Variant); virtual; abstract;
+      function FetchPage(constref AParams: TPageFetchParams; var ADataTable: TDataTable): TPageFetchResult;
+      function GetSearchCapabilities: TSearchCapabilities; virtual; abstract;
+      procedure OnBeforeFetchAll(constref AParams: TPageFetchParams); virtual;
+      procedure FetchAll(const AContainer : TList<T>); virtual; abstract;
+      procedure OnAfterFetchAll(constref AParams: TPageFetchParams); virtual;
+    public
+      constructor Create(AOwner: TComponent); override;
+      destructor Destroy; override;
+  end;
+
+  { Expression API }
+
+  TExpressionKind = (ekUnknown, ekText, ekNum, ekSet);
+  TTextMatchKind = (tmkUnknown, tmkMatchTextExact, tmkMatchTextBeginning, tmkMatchTextEnd, tmkMatchTextAnywhere);
+  TNumericComparisionKind = (nckUnknown, nckNumericEQ, nckNumericLT, nckNumericLTE, nckNumericGT, nckNumericGTE);
+  TSetKind = (skUnknown, skNumericBetweenInclusive, skNumericBetweenExclusive);
+
+  TExpressionRecord = record
+    Values: array of utf8string;
+    HasDecimals: boolean; // for ekSet and ekNum only
+  case Kind: TExpressionKind of
+    ekUnknown: ();
+    ekText: (TextMatchKind: TTextMatchKind);
+    ekNum: (NumericComparisionKind: TNumericComparisionKind);
+    ekSet: (SetKind: TSetKind);
+  end;
+
+  ESearchExpressionParserException = class(Exception);
+
+  { TSearchExpressionService }
+
+  TSearchExpressionService = class
+  public
+    class procedure Parse(const AExpression: utf8string; const AExpressionKind: TExpressionKind; out AExpressionRecord: TExpressionRecord); overload;
+    class function Parse(const AExpression: utf8string): TExpressionRecord; overload;
+  end;
+
+  { TDataSourceTool }
+
+  TDataSourceTool<T> = class
+    protected type
+      __IPredicate_T = IPredicate<T>;
+      __TList_IPredicate_T = TList<__IPredicate_T>;
+    public
+     class function ConstructRowComparer(const AFilters : TArray<TColumnFilter>; const ADelegate : TApplySortDelegate<T>) : IComparer<T>;
+     class function ConstructRowPredicate(const AFilters : TArray<TColumnFilter>; const ADelegate : TApplyFilterDelegate<T>; const AOperand : TFilterOperand) : IPredicate<T>;
+  end;
+
+
+{ RESOURCES }
+resourcestring
+  sAColumnsCantBeNil = 'AColumns can''t be nil!';
+  sTooManyValues = 'Too many values';
+  sInvalidUTF8String = 'Invalid UTF8 string';
+  sBadNumericExpression = 'Bad numeric expression';
+  sUnexpectedNumberFormat = 'Unexpected number format';
+  sBadSyntaxForEscapeCharacter = 'Bad syntax for escape character "\"';
+  sUnexpectedCharInExpression = 'Unexpected char in expression';
+  sInvaildExpression_CharDetectedAfterClosingBracket = 'Invaild expression (char detected after closing bracket)';
+  sUnexpectedTokenFound = 'Unexpected token found : "%s"';
+  sUnexpectedStringLiteralInExpression = 'Unexpected string literal in expression';
+  sBadlyClosedBetweenExpression = 'Badly closed "between" expression';
+  sMissingNumberInExpression = 'Missing number in expression';
+  sUnexpectedOccurrenceOf_Found = 'Unexpected occurrence of "%s" found';
+  sBadBetweenExpression_UnexpectedToken = 'Bad "between" expression. Unexpected "%s"';
+  sExpressionError_NoValue = 'Expression error (no value)';
+
+implementation
+
+uses dateutils, UAutoScope;
+
+{ VARIABLES }
+
+var
+  TableRowType: TTableRow = nil;
+
+{ TTableColumn }
+
+class function TTableColumn.From(AName: AnsiString) : TTableColumn;
+begin
+  Result.Name := AName;
+  Result.Binding := AName;
+end;
+
+class function TTableColumn.From(AName: utf8string; ABinding: AnsiString) : TTableColumn;
+begin
+  Result.Name := AName;
+  Result.Binding := ABinding;
+end;
+
+{ TTableRow }
+
+class constructor TTableRow.Create;
+begin
+  FColumns := TColumnsDictionary.Create([doOwnsValues]);
+end;
+
+class destructor TTableRow.Destroy;
+begin
+  FColumns.Free;
+end;
+
+class function TTableRow.MapColumns(AColumns: PTableColumns): TColumnMapToIndex;
+var
+  i: Integer;
+begin
+  Result := TColumnMapToIndex.Create;
+  for i := 0 to High(AColumns^) do
+    Result.Add(AColumns^[i], i);
+  FColumns.Add(AColumns, Result);
+end;
+
+function TTableRow.GetProperty(var Dest: TVarData;
+  const V: TVarData; const Name: string): Boolean;
+var
+  LRow: TTableRowData absolute V;
+begin
+  Variant(Dest) := LRow.vvalues[LRow.vcolumnmap[Name]];
+  Result := true;
+end;
+
+function TTableRow.SetProperty(var V: TVarData; const Name: string;
+  const Value: TVarData): Boolean;
+var
+  LRow: TTableRowData absolute V;
+begin
+  if NOT LRow.vcolumnmap.ContainsKey(Name) then
+    Exit(true); //raise ETableRow.Create(Format('TableRow did not have column "%s"', [Name]));
+
+  LRow.vvalues[LRow.vcolumnmap[Name]] := Variant(Value);
+  Result := true;
+end;
+
+procedure TTableRow.Clear(var V: TVarData);
+begin
+  Finalize(TTableRowData(V));
+  FillChar(V, SizeOf(V), #0);
+end;
+
+procedure TTableRow.Copy(var Dest: TVarData; const Source: TVarData;
+  const Indirect: Boolean);
+var
+  LDestRow: TTableRowData absolute Dest;
+  LSourceRow: TTableRowData absolute Source;
+begin
+  if Indirect then
+    SimplisticCopy(Dest,Source,true)
+  else
+  begin
+    VarClear(variant(Dest));
+    FillChar(LDestRow, SizeOf(LDestRow), #0);
+    LDestRow.vtype := LSourceRow.vtype;
+    LDestRow.vcolumnmap := LSourceRow.vcolumnmap;
+    LDestRow.vvalues := system.copy(TTableRowData(LSourceRow).vvalues);
+  end;
+end;
+
+function TTableRow.DoFunction(var Dest: TVarData; const V: TVarData;
+  const Name: string; const Arguments: TVarDataArray): Boolean;
+var
+  LRow: TTableRowData absolute V;
+begin
+  Result := (Name = '_') and (Length(Arguments)=1);
+  if Result then
+    Variant(Dest) := LRow.vvalues[Variant(Arguments[0])];
+end;
+
+class function TTableRow.New(AColumns: PTableColumns): Variant;
+var
+  LColumnMap: TColumnMapToIndex;
+begin
+  if not Assigned(AColumns) then
+    raise ETableRow.Create(sAColumnsCantBeNil);
+
+  VarClear(Result);
+  FillChar(Result, SizeOf(Result), #0);
+  TTableRowData(Result).vtype:=TableRowType.VarType;
+
+  if not FColumns.TryGetValue(AColumns, LColumnMap) then
+    LColumnMap := MapColumns(AColumns);
+
+  TTableRowData(Result).vcolumnmap:=LColumnMap;
+  SetLength(TTableRowData(Result).vvalues, Length(AColumns^));
+end;
+
+{ TSearchCapability }
+
+class function TSearchCapability.From(const AName : utf8string; AFilterType : TVisualGridFilters) : TSearchCapability;
+begin
+  Result.ColumnName := AName;
+  Result.SupportedFilters := AFilterType;
+end;
+
+{ TCustomDataSource }
+
+constructor TCustomDataSource<T>.Create(AOwner: TComponent);
+begin
+  inherited Create(AOwner);
+  FLock := TCriticalSection.Create;
+  FColumns := GetColumns;
+end;
+
+destructor TCustomDataSource<T>.Destroy;
+var
+  i : integer;
+  policy : TItemDisposePolicy;
+begin
+  inherited;
+  FreeAndNil(FLock);
+end;
+
+function TCustomDataSource<T>.GetNullPolicy(const AFilter : TColumnFilter) : TSortNullPolicy;
+begin
+  // Rule is as DBMS. When ascending NULLS show last (are greater), when descending, NULLS show first
+  case AFilter.Sort of
+    sdNone: Result := snpNone;
+    sdAscending: Result := snpNullLast;
+    sdDescending: Result := snpNullFirst;
+  end;
+end;
+
+function TCustomDataSource<T>.ApplyColumnSort(constref Left, Right : T; constref AFilter: TColumnFilter) : Integer;
+var
+  leftField, rightField : Variant;
+  leftType, rightType : Integer;
+  nullPolicy : TSortNullPolicy;
+begin
+  leftField := GetItemField(Left, AFilter.ColumnName);
+  rightField := GetItemField(Right, AFilter.ColumnName);
+  leftType := VarType(leftField);
+  rightType := VarType(rightField);
+  nullPolicy := GetNullPolicy(AFilter);
+
+  // Handle null-based corner cases
+  if (leftType = varNull) AND (rightType = varNull) then begin
+    Result := 0;
+    exit;
+  end else if (leftType = varNull) AND (rightType <> varNull) then begin
+    Result := IIF(nullPolicy = snpNullFirst , -1, 1);
+    exit;
+  end else if (leftType <> varNull) AND (rightType = varNull) then begin
+    Result := IIF(nullPolicy = snpNullFirst, 1, -1);
+    exit;
+  end;
+
+  // Compare left/right values
+  Result := TCompare.Variant(@leftField, @rightField);
+
+  // Invert result for descending
+  if AFilter.Sort = sdDescending then
+    Result := Result * -1;
+end;
+
+function TCustomDataSource<T>.ApplyColumnFilter(constref AItem: T; constref AFilter: TColumnFilter) : boolean;
+var
+  value : Variant;
+begin
+  if Length(AFilter.Values) = 0 then
+    raise EArgumentException.Create('AFilter.Values does not contain any parameters');
+
+  value := GetItemField(AItem, AFilter.ColumnName);
+  if AFilter.Filter in TEXT_FILTER then begin
+    case AFilter.Filter of
+      vgfMatchTextExact: Result := TVariantTool.MatchTextExact(value, AFilter.Values[0]);
+      vgfMatchTextBeginning: Result := TVariantTool.MatchTextBeginning(value, AFilter.Values[0]);
+      vgfMatchTextEnd: Result := TVariantTool.MatchTextEnd(value, AFilter.Values[0]);
+      vgfMatchTextAnywhere: Result := TVariantTool.MatchTextAnywhere(value, AFilter.Values[0]);
+    end;
+  end else if AFilter.Filter in NUMERIC_FILTER then begin
+    case AFilter.Filter of
+      vgfNumericEQ: Result := TVariantTool.NumericEQ(value, AFilter.Values[0]);
+      vgfNumericLT: Result := TVariantTool.NumericLT(value, AFilter.Values[0]);
+      vgfNumericLTE: Result := TVariantTool.NumericLTE(value, AFilter.Values[0]);
+      vgfNumericGT: Result := TVariantTool.NumericGT(value, AFilter.Values[0]);
+      vgfNumericGTE: Result := TVariantTool.NumericGTE(value, AFilter.Values[0]);
+      vgfNumericBetweenInclusive: begin
+        if Length(AFilter.Values) < 2 then
+          raise EArgumentException.Create('AFilter.Values does not contain at least 2 parameters');
+        Result := TVariantTool.NumericBetweenInclusive(value, AFilter.Values[0], AFilter.Values[1]);
+      end;
+      vgfNumericBetweenExclusive: begin
+        if Length(AFilter.Values) < 2 then
+          raise EArgumentException.Create('AFilter.Values does not contain at least 2 parameters');
+        Result := TVariantTool.NumericBetweenExclusive(value, AFilter.Values[0], AFilter.Values[1]);
+      end;
+    end;
+  end else Result := false;
+end;
+
+function TCustomDataSource<T>.FetchPage(constref AParams: TPageFetchParams; var ADataTable: TDataTable): TPageFetchResult;
+var
+  i, j : SizeInt;
+  data : TList<T>;
+  GC : TScoped;
+  pageStart, pageEnd : SizeInt;
+  entity : T;
+  comparer : IComparer<T>;
+  filters : TArray<TColumnFilter>;
+  filter : IPredicate<T>;
+begin
+  OnBeforeFetchAll(AParams);
+  FLock.Acquire;
+  try
+     // Fetch underlying data if stale
+     data := GC.AddObject( TList<T>.Create ) as TList<T>;
+     FetchAll(data);
+
+     // Filter the data
+     filters := AParams.GetSearchFilters;
+     if Length(filters) > 0 then
+       TListTool<T>.FilterBy( data, TDataSourceTool<T>.ConstructRowPredicate( filters, ApplyColumnFilter, AParams.Operand ) );
+
+     // Sort the data
+     filters := AParams.GetSortFilters;
+     if Length(filters) > 0 then
+       data.Sort( TDataSourceTool<T>.ConstructRowComparer( filters, ApplyColumnSort ) );
+
+     // Setup result
+     Result.TotalDataCount := data.Count;
+     if (Result.TotalDataCount = 0) then begin
+         Result.PageCount := 0;
+         Result.PageIndex := -1;
+         pageStart := 0;
+         pageEnd := -1;
+     end;
+     Result.PageCount := Ceil (data.Count / (1.0 *AParams.PageSize));
+     if (AParams.PageSize >= 0) and (data.Count > 0) then begin
+       Result.PageIndex := ClipValue(AParams.PageIndex, 0, Result.PageCount - 1);
+       pageStart := Result.PageIndex * AParams.PageSize;
+       pageEnd := ClipValue(pageStart + (AParams.PageSize - 1), pageStart, data.Count - 1);
+     end else begin
+       Result.PageIndex := 0;
+       pageStart := 0;
+       pageEnd := data.Count - 1;
+     end;
+
+     // Dehydrate the page of data only
+
+     // Set columns
+     ADataTable.Columns := FColumns;
+
+     if pageEnd >= pageStart then begin
+       j := 0;
+       SetLength(ADataTable.Rows, pageEnd - pageStart + 1);
+       for i := pageStart to pageEnd do begin
+         ADataTable.Rows[j] := TTableRow.New(@FColumns);
+         DehydrateItem( data[i], ADataTable.Rows[j]);
+         inc(j)
+       end;
+     end;
+  finally
+    FLock.Release;
+  end;
+  OnAfterFetchAll(AParams);
+end;
+
+procedure TCustomDataSource<T>.OnBeforeFetchAll(constref AParams: TPageFetchParams);
+begin
+end;
+
+procedure TCustomDataSource<T>.OnAfterFetchAll(constref AParams: TPageFetchParams);
+begin
+end;
+
+{ TColumnFilterPredicate }
+
+constructor TColumnFilterPredicate<T>.Create(const AFilter : TColumnFilter; const ADelegate : TApplyFilterDelegate<T>);
+begin
+  FFilter := AFilter;
+  FDelegate := ADelegate;
+end;
+
+function TColumnFilterPredicate<T>.Evaluate (constref AValue: T) : boolean;
+begin
+  Result := FDelegate(AValue, FFilter);
+end;
+
+{ TColumnFieldComparer }
+
+constructor TColumnFieldComparer<T>.Create(const AFilter : TColumnFilter; const ADelegate: TApplySortDelegate<T>);
+begin
+  FFilter := AFilter;
+  FDelegate := ADelegate;
+end;
+
+function TColumnFieldComparer<T>.Compare(constref ALeft, ARight: T): Integer;
+begin
+  Result := FDelegate(ALeft, ARight, FFilter);
+end;
+
+{ TDataSourceTool }
+
+class function TDataSourceTool<T>.ConstructRowComparer(const AFilters : TArray<TColumnFilter>; const ADelegate : TApplySortDelegate<T>) : IComparer<T>;
+type
+  __IComparer_T = IComparer<T>;
+var
+  i : integer;
+  comparers : TList<__IComparer_T>;
+  filter : TColumnFilter;
+  GC : TScoped;
+begin
+  comparers := GC.AddObject(  TList<__IComparer_T>.Create ) as TList<__IComparer_T>;
+  for i := Low(AFilters) to High(AFilters) do begin
+    filter := AFilters[i];
+    if filter.Sort <> sdNone then
+      comparers.Add( TColumnFieldComparer<T>.Create( filter, ADelegate ) );
+  end;
+
+  case comparers.Count of
+    0: Result := nil;
+    1: Result := comparers[0];
+    else Result := TComparerTool<T>.Many(comparers);
+  end;
+end;
+
+class function TDataSourceTool<T>.ConstructRowPredicate(const AFilters : TArray<TColumnFilter>; const ADelegate : TApplyFilterDelegate<T>; const AOperand : TFilterOperand) : IPredicate<T>;
+type
+  __TColumnFilterPredicate_T = TColumnFilterPredicate<T>;
+  __TPredicateTool_T = TPredicateTool<T>;
+var
+  i : integer;
+  filters : __TList_IPredicate_T;
+  GC : TScoped;
+begin
+  filters := GC.AddObject( __TList_IPredicate_T.Create ) as __TList_IPredicate_T;
+  for i := Low(AFilters) to High(AFilters) do begin
+    if AFilters[i].Filter <> vgfSortable then begin
+      filters.Add( __TColumnFilterPredicate_T.Create(AFilters[i], ADelegate));
+    end;
+  end;
+
+  case filters.Count of
+    0: Result := nil;
+    1: Result := filters[0];
+    else case AOperand of
+      foAnd: Result := __TPredicateTool_T.AndMany(filters.ToArray);
+      foOr: Result := __TPredicateTool_T.OrMany(filters.ToArray);
+    end;
+  end;
+end;
+
+{ TPageFetchParams }
+
+constructor TPageFetchParams.Create(AIndex: Integer; ASize: Integer; AFilter: TFilterCriteria; AOperand : TFilterOperand);
+begin
+  PageIndex:= AIndex;
+  PageSize:=ASize;
+  Filter:=AFilter;
+  Operand := AOperand;
+end;
+
+function TPageFetchParams.GetSortFilters : TArray<TColumnFilter>;
+var
+  sortFilters : TList<TColumnFilter>;
+  GC : TScoped;
+
+  function IsSortFilter(constref AColFilter : TColumnFilter) : boolean;
+  begin
+    Result := AColFilter.Sort <> sdNone;
+  end;
+
+begin
+  sortFilters := GC.AddObject( TList<TColumnFilter>.Create ) as TList<TColumnFilter>;
+  sortFilters.AddRange( Filter);
+  TListTool<TColumnFilter>.FilterBy( sortFilters, TPredicateTool<TColumnFilter>.FromFunc( IsSortFilter ));
+  SetLength(Result, 0);
+  if sortFilters.Count > 0 then
+    Result := sortFilters.ToArray;
+end;
+
+function TPageFetchParams.GetSearchFilters : TArray<TColumnFilter>;
+var
+  searchFilters : TList<TColumnFilter>;
+  GC : TScoped;
+
+  function IsSearchFilter(constref AColFilter : TColumnFilter) : boolean;
+  begin
+    Result := AColFilter.Filter <> vgfSortable;
+  end;
+
+begin
+  searchFilters := GC.AddObject( TList<TColumnFilter>.Create ) as TList<TColumnFilter>;
+  searchFilters.AddRange(Filter);
+  TListTool<TColumnFilter>.FilterBy( searchFilters, TPredicateTool<TColumnFilter>.FromFunc( IsSearchFilter ));
+  SetLength(Result, 0);
+  if searchFilters.Count > 0 then
+    Result := searchFilters.ToArray;
+end;
+
+{ TSearchExpressionService }
+
+class procedure TSearchExpressionService.Parse(const AExpression: utf8string;
+  const AExpressionKind: TExpressionKind; out
+  AExpressionRecord: TExpressionRecord);
+const
+  MAX_VALUES = 2;
+type
+  TToken = (tkNone, tkPercent, tkLess, tkGreater, tkEqual, tkLessOrEqual,
+    tkGreaterOrEqual, tkOpeningParenthesis, tkClosingParenthesis,
+    tkOpeningBracket, tkClosingBracket, tkText, tkNum, tkComma);
+
+  TUTF8Char = record
+    Length: byte;
+    Char: array [0..3] of AnsiChar;
+  end;
+
+const
+  CONVERTABLE_TOKENS_TO_STR = [tkLess..tkClosingBracket, tkComma];
+  NUM_OPERATORS = [tkLess..tkGreaterOrEqual];
+
+  procedure GetChar(APos: PAnsiChar; out AChar: TUTF8Char);
+  begin
+    AChar.Length := UTF8CharacterLength(APos);
+
+    if AChar.Length >= 1 then AChar.Char[0] := APos[0];
+    if AChar.Length >= 2 then AChar.Char[1] := APos[1];
+    if AChar.Length >= 3 then AChar.Char[2] := APos[2];
+    if AChar.Length = 4 then AChar.Char[3] := APos[3];
+  end;
+
+  function TokenToStr(AToken: TToken): utf8string;
+  const
+    CONVERTER: array[TToken] of utf8string = (
+      'NONE', '%', '<', '>', '=', '<=', '>=', '(', ')', '[', ']', 'TEXT',
+      'NUMBER', ','
+    );
+  begin
+    Result := CONVERTER[AToken];
+  end;
+
+var
+  c, nc: PAnsiChar;
+  i: Integer;
+  LDot: boolean = false;
+  LChar: TUTF8Char;
+  LValueIdx: Integer = -1;
+  LValues: array[0..MAX_VALUES-1] of utf8string; // for now only 2 values for set "between"
+  LValue: PUTF8String;
+  LToken: TToken = tkNone;
+  LPrevToken: TToken = tkNone;
+  LExpression: utf8string;
+  LLastPercent: boolean = false;
+  LExpressionKind: TExpressionKind;
+  LWasUnknow: boolean;
+  LHasDecimals: boolean = false;
+
+  procedure NextValue;
+  begin
+    Inc(LValueIdx);
+    if LValueIdx > MAX_VALUES - 1 then
+      raise ESearchExpressionParserException.Create(sTooManyValues);
+    LValue := @LValues[LValueIdx];
+  end;
+
+  procedure EscapeSequence(AChar: Char);
+  begin
+    LToken := tkText;
+    LValue^ := LValue^ + AChar;
+    Inc(c);
+  end;
+
+begin
+  AExpressionRecord := Default(TExpressionRecord);
+  if AExpression = '' then
+    Exit;
+
+  LWasUnknow := AExpressionKind = ekUnknown;
+  LExpressionKind := AExpressionKind;
+  // more simple parsing loop
+  if AExpressionKind in [ekSet, ekNum] then
+    LExpression:=Trim(AExpression)
+  else
+    LExpression:=AExpression;
+
+  c := @LExpression[1];
+  if FindInvalidUTF8Character(c, Length(LExpression)) <> -1 then
+    raise ESearchExpressionParserException.Create(sInvalidUTF8String);
+
+  NextValue;
+  repeat
+    // simple scanner
+    GetChar(c, LChar);
+    if LChar.Length = 1 then
+      case LChar.Char[0] of
+        #0: Break;
+        #1..#32:
+          case LExpressionKind of
+            ekSet:
+              begin
+                while c^ in [#1..#32] do Inc(c);
+                Continue;
+              end;
+            ekText, ekUnknown:
+              begin
+                LValue^:=LValue^+LChar.Char[0];
+                LToken:=tkText;
+              end;
+            ekNum:
+              if not (LPrevToken in NUM_OPERATORS) then
+                raise ESearchExpressionParserException.Create(sBadNumericExpression)
+              else
+              begin
+                while c^ in [#1..#32] do Inc(c);
+                continue;
+              end;
+          end;
+        '0'..'9':
+          begin
+            repeat
+              if c^ = '.' then
+                if LDot then
+                  raise ESearchExpressionParserException.Create(sUnexpectedNumberFormat)
+                else
+                  LDot:=true;
+              LValue^:=LValue^+c^;
+              Inc(c);
+            until not (c^ in ['0'..'9', '.']);
+            Dec(c);
+            case LExpressionKind of
+              ekUnknown, ekSet, ekNum: LToken := tkNum;
+              ekText: LToken := tkText;
+            end;
+            if LWasUnknow and LDot then
+              LHasDecimals := true;
+            LDot := false;
+          end;
+        '%':
+          begin
+            if not (LExpressionKind in [ekText,ekUnknown]) then
+              ESearchExpressionParserException.Create(sBadNumericExpression);
+
+            LToken := tkPercent;
+          end;
+        '\':
+          begin
+            if not (LExpressionKind in [ekText,ekUnknown]) then
+              ESearchExpressionParserException.Create(sBadNumericExpression);
+            case (c+1)^ of
+              '%': EscapeSequence('%');
+              '\': EscapeSequence('\');
+              '[': EscapeSequence('[');
+              '(': EscapeSequence('(');
+              ']': EscapeSequence(']');
+              ')': EscapeSequence(')');
+              '<': EscapeSequence('<');
+              '>': EscapeSequence('>');
+              '=': EscapeSequence('=');
+            else
+              raise ESearchExpressionParserException.Create(sBadSyntaxForEscapeCharacter);
+            end
+          end;
+        '<':
+          if (c+1)^ = '=' then
+          begin
+            LToken := tkLessOrEqual;
+            Inc(c);
+          end
+          else
+            LToken := tkLess;
+        '>':
+          if (c+1)^ = '=' then
+          begin
+            LToken := tkGreaterOrEqual;
+            Inc(c);
+          end
+          else
+            LToken := tkGreater;
+        '=': LToken := tkEqual;
+        '(': LToken := tkOpeningParenthesis;
+        ')': LToken := tkClosingParenthesis;
+        '[': LToken := tkOpeningBracket;
+        ']': LToken := tkClosingBracket;
+        ',': LToken := tkComma;
+      else
+        LValue^ := LValue^ + LChar.Char[0];
+        LToken:=tkText;
+      end
+    else
+    begin
+      if not (LExpressionKind in [ekUnknown, ekText]) then
+        raise ESearchExpressionParserException.Create(sUnexpectedCharInExpression);
+      SetLength(LValue^, Length(LValue^) + LChar.Length);
+      Move(LChar.Char[0], LValue^[Succ(Length(LValue^) - LChar.Length)], LChar.Length);
+      LToken:=tkText;
+    end;
+
+    // parser is able to deduce expression kind (if needed)
+    if LExpressionKind = ekUnknown then
+    case LToken of
+      tkPercent, tkText, tkComma: LExpressionKind:=ekText;
+      tkOpeningBracket, tkOpeningParenthesis: LExpressionKind:=ekSet;
+      tkLess..tkGreaterOrEqual, tkNum: LExpressionKind:=ekNum;
+    else
+      raise ESearchExpressionParserException.Create(sUnexpectedCharInExpression);
+    end;
+
+    // text mode has precedence (parsing the expressions like: 123abs)
+    if (LExpressionKind = ekNum) and (AExpressionKind = ekUnknown)
+      and (LToken in [tkText, tkPercent]) and (AExpressionRecord.NumericComparisionKind = nckUnknown) then
+    begin
+      LExpressionKind := ekText;
+    end;
+
+    // text mode is special so part of tokens are used as normal characters
+    if (LExpressionKind = ekText) and (LToken in CONVERTABLE_TOKENS_TO_STR) then
+      LValue^:=LValue^+TokenToStr(LToken);
+
+    if LPrevToken in [tkClosingBracket, tkClosingParenthesis] then
+      raise ESearchExpressionParserException.Create(sInvaildExpression_CharDetectedAfterClosingBracket);
+
+    // rules
+    case LToken of
+      tkNum:
+        if LExpressionKind = ekSet then
+          if not (LPrevToken in [tkOpeningBracket, tkOpeningParenthesis, tkComma]) then
+            raise ESearchExpressionParserException.CreateFmt(sUnexpectedTokenFound, [TokenToStr(LToken)]);
+      tkText:
+        if LExpressionKind in [ekSet, ekNum] then
+          raise ESearchExpressionParserException.Create(sUnexpectedStringLiteralInExpression);
+      tkClosingBracket:
+        if (LExpressionKind = ekSet) then
+          if (AExpressionRecord.SetKind<>skNumericBetweenInclusive) then
+            raise ESearchExpressionParserException.Create(sBadlyClosedBetweenExpression)
+          else if LPrevToken <> tkNum then
+            raise ESearchExpressionParserException.Create(sMissingNumberInExpression);
+      tkClosingParenthesis:
+        if (LExpressionKind = ekSet) then
+          if (AExpressionRecord.SetKind<>skNumericBetweenExclusive) then
+            raise ESearchExpressionParserException.Create(sBadlyClosedBetweenExpression)
+          else if LPrevToken <> tkNum then
+            raise ESearchExpressionParserException.Create(sMissingNumberInExpression);
+      tkComma:
+        if LExpressionKind = ekSet then
+          if not (LPrevToken = tkNum) then
+            raise ESearchExpressionParserException.CreateFmt(sUnexpectedOccurrenceOf_Found, [','])
+          else
+            NextValue;
+      tkPercent:
+        if LExpressionKind = ekText then
+        begin
+          if LLastPercent then
+            raise ESearchExpressionParserException.CreateFmt(sUnexpectedOccurrenceOf_Found, ['%']);
+          case LPrevToken of
+            tkText, tkNum: // tkNum is here because is possible to parse: 123%
+              begin
+                if (AExpressionRecord.TextMatchKind = tmkUnknown) then
+                  AExpressionRecord.TextMatchKind:=tmkMatchTextBeginning
+                else
+                  AExpressionRecord.TextMatchKind:=tmkMatchTextAnywhere;
+                LLastPercent:=true;
+              end;
+            tkNone:
+              AExpressionRecord.TextMatchKind:=tmkMatchTextEnd;
+            tkPercent:
+              raise ESearchExpressionParserException.CreateFmt(sUnexpectedOccurrenceOf_Found, ['%']);
+          end;
+        end
+        else
+          raise ESearchExpressionParserException.CreateFmt(sUnexpectedOccurrenceOf_Found, ['%']);
+      tkLess..tkGreaterOrEqual:
+        case LExpressionKind of
+          ekNum:
+            if LPrevToken <> tkNone then
+              raise ESearchExpressionParserException.Create(sBadNumericExpression)
+            else
+              with AExpressionRecord do
+              case LToken of
+                tkLess: NumericComparisionKind:=nckNumericLT;
+                tkGreater: NumericComparisionKind:=nckNumericGT;
+                tkEqual: NumericComparisionKind:=nckNumericEQ;
+                tkLessOrEqual: NumericComparisionKind:=nckNumericLTE;
+                tkGreaterOrEqual: NumericComparisionKind:=nckNumericGTE;
+              end;
+          ekSet:
+            raise ESearchExpressionParserException.CreateFmt(sUnexpectedTokenFound, [TokenToStr(LToken)]);
+        end;
+      tkOpeningParenthesis, tkOpeningBracket:
+        if LExpressionKind = ekSet then
+          if LPrevToken <> tkNone then
+            raise ESearchExpressionParserException.CreateFmt(sBadBetweenExpression_UnexpectedToken, [TokenToStr(LToken)])
+          else
+          with AExpressionRecord do
+          case LToken of
+            tkOpeningParenthesis: SetKind:=skNumericBetweenExclusive;
+            tkOpeningBracket: SetKind:=skNumericBetweenInclusive;
+          end;
+    end;
+    LPrevToken := LToken;
+    Inc(c, LChar.Length);
+  until (LChar.Length=0) or (c^ = #0);
+
+  case LExpressionKind of
+    ekText:
+      if AExpressionRecord.TextMatchKind = tmkUnknown then
+        AExpressionRecord.TextMatchKind:=tmkMatchTextExact;
+    ekSet:
+      case AExpressionRecord.SetKind of
+        skNumericBetweenInclusive:
+          if LPrevToken <> tkClosingBracket then
+            raise ESearchExpressionParserException.Create(sBadlyClosedBetweenExpression);
+        skNumericBetweenExclusive:
+          if LPrevToken <> tkClosingParenthesis then
+            raise ESearchExpressionParserException.Create(sBadlyClosedBetweenExpression);
+      end;
+  end;
+
+  if (LValueIdx = 0) and (LValue^='') then
+    raise ESearchExpressionParserException.Create(sExpressionError_NoValue);
+
+  SetLength(AExpressionRecord.Values, LValueIdx + 1);
+  for i := 0 to LValueIdx do
+    AExpressionRecord.Values[i] := LValues[i];
+
+  AExpressionRecord.Kind := LExpressionKind;
+  AExpressionRecord.HasDecimals := LHasDecimals;
+end;
+
+class function TSearchExpressionService.Parse(const AExpression: utf8string
+  ): TExpressionRecord;
+begin
+  Result.Kind := ekUnknown;
+  Parse(AExpression, Result.Kind, Result);
+end;
+
+initialization
+  TableRowType := TTableRow.Create;
+
+finalization
+  TableRowType.Free;
+end.
+

+ 175 - 0
Units/Utils/UCommon.UI.pas

@@ -0,0 +1,175 @@
+{
+  Copyright (c) 2017 - 2018 Sphere 10 Software
+
+  Common GUI unit usable across all tiers.
+
+  Distributed under the MIT software license, see the accompanying file LICENSE
+  or visit http://www.opensource.org/licenses/mit-license.php.
+
+  Acknowledgements:
+    Herman Schoenfeld
+}
+
+unit UCommon.UI;
+
+{$mode delphi}
+
+interface
+
+uses
+  Classes, SysUtils, Forms, Controls,ExtCtrls,  FGL, Graphics, Generics.Collections, Generics.Defaults, syncobjs;
+
+type
+  TApplicationForm = class(TForm)
+    private
+      FActivatedCount : UInt32;
+      FActivateFirstTime : TNotifyEvent;
+      FDestroyed : TNotifyEvent;
+      FCloseAction : TCloseAction;
+      procedure NotifyActivateFirstTime;
+      procedure NotifyDestroyed;
+    protected
+      FUILock : TCriticalSection;
+      procedure DoCreate; override;
+      procedure Activate; override;
+      procedure ActivateFirstTime; virtual;
+      procedure DoClose(var CloseAction: TCloseAction); override;
+      procedure DoDestroy; override;
+      procedure DoDestroyed; virtual;
+    published
+      property CloseAction : TCloseAction read FCloseAction write FCloseAction;
+      property OnActivateFirstTime : TNotifyEvent read FActivateFirstTime write FActivateFirstTime;
+      property OnDestroyed : TNotifyEvent read FDestroyed write FDestroyed;
+    public
+      property ActivationCount : UInt32 read FActivatedCount;
+  end;
+
+  { TWinControlHelper }
+
+  TWinControlHelper = class helper for TWinControl
+    procedure RemoveAllControls(destroy : boolean);
+    procedure AddControlDockCenter(AControl: TWinControl);
+  end;
+
+  { TFormHelper }
+
+  TFormHelper = class helper for TForm
+  end;
+
+  { TImageHelper }
+
+  TImageHelper = class helper for TImage
+    procedure SetImageListPicture(AImageList: TImageList; AIndex : SizeInt);
+  end;
+
+
+implementation
+
+{%region TApplicationForm}
+
+procedure TApplicationForm.DoCreate;
+begin
+  inherited;
+  FUILock := TCriticalSection.Create;
+  FActivatedCount := 0;
+  FCloseAction:=caHide;
+end;
+
+procedure TApplicationForm.Activate;
+begin
+  inherited;
+  inc(FActivatedCount);
+  if (FActivatedCount = 1) then
+    NotifyActivateFirstTime;
+end;
+
+procedure TApplicationForm.ActivateFirstTime;
+begin;
+end;
+
+procedure TApplicationForm.DoClose(var CloseAction: TCloseAction);
+begin
+  CloseAction := FCloseAction;
+end;
+
+procedure TApplicationForm.DoDestroy;
+begin
+  inherited;
+  FActivatedCount:=0;
+  FUILock.Destroy;
+  FUILock := nil;
+  NotifyDestroyed;
+end;
+
+procedure TApplicationForm.DoDestroyed;
+begin;
+end;
+
+procedure TApplicationForm.NotifyActivateFirstTime;
+begin
+  ActivateFirstTime;
+  if Assigned(FActivateFirstTime) then
+    FActivateFirstTime(Self);
+end;
+
+procedure TApplicationForm.NotifyDestroyed;
+begin
+  DoDestroyed;
+  if Assigned(FDestroyed) then
+    FDestroyed(Self);
+end;
+
+{%endregion}
+
+{%region TWinControlHelper}
+
+procedure TWinControlHelper.RemoveAllControls(destroy : boolean);
+var
+  control : TControl;
+begin
+  while self.ControlCount > 0 do begin
+    control := self.Controls[0];
+    self.RemoveControl(control);
+    if destroy then control.Destroy;
+  end;
+end;
+
+procedure TWinControlHelper.AddControlDockCenter(AControl: TWinControl);
+begin
+  if AControl.ClassType.InheritsFrom(TCustomForm) then begin
+    with TCustomForm(AControl) do begin
+      // Needed to avoid infinite WMSize loop
+      Constraints.MinHeight := 0;
+      Constraints.MaxHeight := 0;
+      Constraints.MinWidth := 0;
+      Constraints.MaxWidth := 0;
+      Anchors := [akTop, akLeft, akRight, akBottom];
+    end;
+  end;
+
+  with AControl do begin
+    Align := alClient;
+    Parent := Self;
+    Show;
+  end;
+end;
+
+{%endregion}
+
+{%region TFormHelper}
+
+
+{%endregion}
+
+{%region TImageHelper}
+
+procedure TImageHelper.SetImageListPicture(AImageList: TImageList; AIndex : SizeInt);
+begin
+  AImageList.GetBitmap(AIndex, Self.Picture.Bitmap);
+end;
+
+{%endregion}
+
+end.
+
+

File diff suppressed because it is too large
+ 968 - 186
Units/Utils/UCommon.pas


File diff suppressed because it is too large
+ 431 - 123
Units/Utils/UVisualGrid.pas


+ 2 - 2
Units/Utils/UWizard.lfm

@@ -1,7 +1,7 @@
 object WizardHostForm: TWizardHostForm
 object WizardHostForm: TWizardHostForm
-  Left = 993
+  Left = 187
   Height = 120
   Height = 120
-  Top = 640
+  Top = 35
   Width = 360
   Width = 360
   BorderIcons = [biSystemMenu]
   BorderIcons = [biSystemMenu]
   Caption = 'Wizard'
   Caption = 'Wizard'

+ 16 - 16
Units/Utils/UWizard.pas

@@ -135,21 +135,21 @@ type
         procedure UpdatePath(APathUpdateType: TPathUpdateType; const screens : array of TComponentClass); virtual;
         procedure UpdatePath(APathUpdateType: TPathUpdateType; const screens : array of TComponentClass); virtual;
   end;
   end;
 
 
-  { TActioWizard Delegate Declarations }
-  TActionWizardCancelFunc<T> = function(screenIndex : Integer; constref model : T; out message : AnsiString) : boolean of object;
-  TActionWizardFinishFunc<T> = function(constref  model : T; out message : AnsiString) : boolean of object;
 
 
   { TActionWizard - a generic Wizard that can be used without subclassing }
   { TActionWizard - a generic Wizard that can be used without subclassing }
   TActionWizard<T> = class(specialize TWizard<T>)
   TActionWizard<T> = class(specialize TWizard<T>)
+    public type
+      TActionWizardCancelFunc = function(screenIndex : Integer; constref model : T; out message : AnsiString) : boolean of object;
+      TActionWizardFinishFunc = function(constref  model : T; out message : AnsiString) : boolean of object;
     private
     private
-      FCancelEvent : TActionWizardCancelFunc<T>;
-      FFinishEvent : TActionWizardFinishFunc<T>;
+      FCancelHandler : TActionWizardCancelFunc;
+      FFinishHandler : TActionWizardFinishFunc;
     protected
     protected
       function CancelRequested(out message : AnsiString) : boolean; override;
       function CancelRequested(out message : AnsiString) : boolean; override;
       function FinishRequested(out message : AnsiString) : boolean; override;
       function FinishRequested(out message : AnsiString) : boolean; override;
     public
     public
-      constructor Create(AOwner:TComponent; title, finish: AnsiString; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc<T>; finishFunc : TActionWizardFinishFunc<T>);
-      class procedure Show(AParent: TForm; title, finish: AnsiString; constref bag : T; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc<T>; finishFunc : TActionWizardFinishFunc<T>);
+      constructor Create(AOwner:TComponent; title, finish: AnsiString; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc; finishFunc : TActionWizardFinishFunc);
+      class procedure Show(AParent: TForm; title, finish: AnsiString; constref bag : T; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc; finishFunc : TActionWizardFinishFunc);
       property FinishText : AnsiString read FTitleText;
       property FinishText : AnsiString read FTitleText;
       property TitleText : AnsiString read FFinishText;
       property TitleText : AnsiString read FFinishText;
   end;
   end;
@@ -162,7 +162,7 @@ implementation
 {$R *.lfm}
 {$R *.lfm}
 
 
 uses
 uses
-  UCommonUI;
+  UCommon.UI;
 
 
 {%region TWizardForm }
 {%region TWizardForm }
 
 
@@ -518,16 +518,16 @@ end;
 
 
 {%region TActionWizard }
 {%region TActionWizard }
 
 
-constructor TActionWizard<T>.Create(AOwner: TComponent; title, finish: AnsiString; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc<T>; finishFunc : TActionWizardFinishFunc<T>);
+constructor TActionWizard<T>.Create(AOwner: TComponent; title, finish: AnsiString; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc; finishFunc : TActionWizardFinishFunc);
 begin
 begin
   inherited Create(AOwner, screens);
   inherited Create(AOwner, screens);
   self.FTitleText := title;
   self.FTitleText := title;
   self.FFinishText := finishText;
   self.FFinishText := finishText;
-  self.FCancelEvent := cancelFunc;
-  self.FFinishEvent := finishFunc;
+  self.FCancelHandler := cancelFunc;
+  self.FFinishHandler := finishFunc;
 end;
 end;
 
 
-class procedure TActionWizard<T>.Show(AParent: TForm; title, finish: AnsiString; constref bag : T; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc<T>; finishFunc : TActionWizardFinishFunc<T>);
+class procedure TActionWizard<T>.Show(AParent: TForm; title, finish: AnsiString; constref bag : T; const screens : array of TComponentClass; cancelFunc: TActionWizardCancelFunc; finishFunc : TActionWizardFinishFunc);
 type
 type
   MyWizard = TActionWizard<T>;
   MyWizard = TActionWizard<T>;
 var
 var
@@ -543,15 +543,15 @@ end;
 
 
 function TActionWizard<T>.CancelRequested(out message : AnsiString) : boolean;
 function TActionWizard<T>.CancelRequested(out message : AnsiString) : boolean;
 begin
 begin
-  if Assigned(FCancelEvent) and NOT FFinished then
-    Result := FCancelEvent(Self.FCurrentScreenIndex, self.Model, message)
+  if Assigned(FCancelHandler) and NOT FFinished then
+    Result := FCancelHandler(Self.FCurrentScreenIndex, self.Model, message)
   else Result := true;
   else Result := true;
 end;
 end;
 
 
 function TActionWizard<T>.FinishRequested(out message : AnsiString) : boolean;
 function TActionWizard<T>.FinishRequested(out message : AnsiString) : boolean;
 begin
 begin
-  if Assigned(FFinishEvent) then
-    Result := FFinishEvent(self.Model, message)
+  if Assigned(FFinishHandler) then
+    Result := FFinishHandler(self.Model, message)
   else Result := true;
   else Result := true;
   FFinished := true;
   FFinished := true;
 end;
 end;

+ 187 - 59
Units/Utils/generics.collections.pas

@@ -210,6 +210,8 @@ type
     property Count: SizeInt read GetCount;
     property Count: SizeInt read GetCount;
     property Capacity: SizeInt read GetCapacity write SetCapacity;
     property Capacity: SizeInt read GetCapacity write SetCapacity;
     property OnNotify: TCollectionNotifyEvent<T> read FOnNotify write FOnNotify;
     property OnNotify: TCollectionNotifyEvent<T> read FOnNotify write FOnNotify;
+
+    procedure TrimExcess; virtual; abstract;
   end;
   end;
 
 
   TCustomListEnumerator<T> = class abstract(TEnumerator<T>)
   TCustomListEnumerator<T> = class abstract(TEnumerator<T>)
@@ -307,7 +309,7 @@ type
 
 
     procedure Reverse;
     procedure Reverse;
 
 
-    procedure TrimExcess;
+    procedure TrimExcess; override;
 
 
     procedure Sort; overload;
     procedure Sort; overload;
     procedure Sort(const AComparer: IComparer<T>); overload;
     procedure Sort(const AComparer: IComparer<T>); overload;
@@ -406,7 +408,7 @@ type
     function Extract: T;
     function Extract: T;
     function Peek: T;
     function Peek: T;
     procedure Clear;
     procedure Clear;
-    procedure TrimExcess;
+    procedure TrimExcess; override;
   end;
   end;
 
 
   TStack<T> = class(TCustomListWithPointers<T>)
   TStack<T> = class(TCustomListWithPointers<T>)
@@ -434,7 +436,7 @@ type
     function Pop: T; inline;
     function Pop: T; inline;
     function Peek: T;
     function Peek: T;
     function Extract: T; inline;
     function Extract: T; inline;
-    procedure TrimExcess;
+    procedure TrimExcess; override;
   end;
   end;
 
 
   TObjectList<T: class> = class(TList<T>)
   TObjectList<T: class> = class(TList<T>)
@@ -507,6 +509,8 @@ type
   protected
   protected
     function DoGetEnumerator: TEnumerator<T>; override;
     function DoGetEnumerator: TEnumerator<T>; override;
     function GetCount: SizeInt; virtual; abstract;
     function GetCount: SizeInt; virtual; abstract;
+    function GetCapacity: SizeInt; virtual; abstract;
+    procedure SetCapacity(AValue: SizeInt); virtual; abstract;
     function GetOnNotify: TCollectionNotifyEvent<T>; virtual; abstract;
     function GetOnNotify: TCollectionNotifyEvent<T>; virtual; abstract;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); virtual; abstract;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); virtual; abstract;
   public
   public
@@ -535,6 +539,8 @@ type
     procedure SymmetricExceptWith(AHashSet: TCustomSet<T>);
     procedure SymmetricExceptWith(AHashSet: TCustomSet<T>);
 
 
     property Count: SizeInt read GetCount;
     property Count: SizeInt read GetCount;
+    property Capacity: SizeInt read GetCapacity write SetCapacity;
+    procedure TrimExcess; virtual; abstract;
 
 
     property OnNotify: TCollectionNotifyEvent<T> read GetOnNotify write SetOnNotify;
     property OnNotify: TCollectionNotifyEvent<T> read GetOnNotify write SetOnNotify;
   end;
   end;
@@ -566,6 +572,8 @@ type
   protected
   protected
     function GetPtrEnumerator: TEnumerator<PT>; override;
     function GetPtrEnumerator: TEnumerator<PT>; override;
     function GetCount: SizeInt; override;
     function GetCount: SizeInt; override;
+    function GetCapacity: SizeInt; override;
+    procedure SetCapacity(AValue: SizeInt); override;
     function GetOnNotify: TCollectionNotifyEvent<T>; override;
     function GetOnNotify: TCollectionNotifyEvent<T>; override;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); override;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); override;
   public
   public
@@ -580,6 +588,8 @@ type
 
 
     procedure Clear; override;
     procedure Clear; override;
     function Contains(constref AValue: T): Boolean; override;
     function Contains(constref AValue: T): Boolean; override;
+
+    procedure TrimExcess; override;
   end;
   end;
 
 
   TPair<TKey, TValue, TInfo> = record
   TPair<TKey, TValue, TInfo> = record
@@ -653,11 +663,11 @@ type
   public type
   public type
     TNode = TAVLTreeNode<TREE_CONSTRAINTS, TTree>;
     TNode = TAVLTreeNode<TREE_CONSTRAINTS, TTree>;
     PNode = ^TNode;
     PNode = ^TNode;
+    PPNode = ^PNode;
     TTreePair = TPair<TKey, TValue>;
     TTreePair = TPair<TKey, TValue>;
     PKey = ^TKey;
     PKey = ^TKey;
     PValue = ^TValue;
     PValue = ^TValue;
   private type
   private type
-    PPNode = ^PNode;
     // type exist only for generic constraint in TNodeCollection (non functional - PPNode has no sense)
     // type exist only for generic constraint in TNodeCollection (non functional - PPNode has no sense)
     TPNodeEnumerator = class(TAVLTreeEnumerator<PPNode, PNode, TTree>);
     TPNodeEnumerator = class(TAVLTreeEnumerator<PPNode, PNode, TTree>);
   private var
   private var
@@ -675,8 +685,6 @@ type
     procedure NodeAdded(ANode: PNode); virtual;
     procedure NodeAdded(ANode: PNode); virtual;
     procedure DeletingNode(ANode: PNode; AOrigin: boolean); virtual;
     procedure DeletingNode(ANode: PNode; AOrigin: boolean); virtual;
 
 
-    function AddNode: PNode; virtual; abstract;
-
     function DoRemove(ANode: PNode; ACollectionNotification: TCollectionNotification; ADispose: boolean): TValue;
     function DoRemove(ANode: PNode; ACollectionNotification: TCollectionNotification; ADispose: boolean): TValue;
     procedure DisposeAllNodes(ANode: PNode); overload;
     procedure DisposeAllNodes(ANode: PNode); overload;
 
 
@@ -738,7 +746,8 @@ type
   private
   private
     FNodes: TNodeCollection;
     FNodes: TNodeCollection;
     function GetNodeCollection: TNodeCollection;
     function GetNodeCollection: TNodeCollection;
-    procedure InternalAdd(ANode, AParent: PNode);
+    procedure InternalAdd(ANode, AParent: PNode); overload;
+    function InternalAdd(ANode: PNode; ADispisable: boolean): PNode; overload;
     procedure InternalDelete(ANode: PNode);
     procedure InternalDelete(ANode: PNode);
     function GetKeys: TKeyCollection;
     function GetKeys: TKeyCollection;
     function GetValues: TValueCollection;
     function GetValues: TValueCollection;
@@ -746,8 +755,17 @@ type
     constructor Create; virtual; overload;
     constructor Create; virtual; overload;
     constructor Create(const AComparer: IComparer<TKey>); virtual; overload;
     constructor Create(const AComparer: IComparer<TKey>); virtual; overload;
 
 
+    function NewNode: PNode;
+    function NewNodeArray(ACount: SizeInt): PNode; overload;
+    procedure NewNodeArray(out AArray: TArray<PNode>; ACount: SizeInt); overload;
+    procedure DisposeNode(ANode: PNode);
+    procedure DisposeNodeArray(ANode: PNode; ACount: SizeInt); overload;
+    procedure DisposeNodeArray(var AArray: TArray<PNode>); overload;
+
     destructor Destroy; override;
     destructor Destroy; override;
-    function Add(constref AKey: TKey; constref AValue: TValue): PNode;
+    function AddNode(ANode: PNode): boolean; overload; inline;
+    function Add(constref APair: TTreePair): PNode; overload; inline;
+    function Add(constref AKey: TKey; constref AValue: TValue): PNode; overload; inline;
     function Remove(constref AKey: TKey; ADisposeNode: boolean = true): boolean;
     function Remove(constref AKey: TKey; ADisposeNode: boolean = true): boolean;
     function ExtractPair(constref AKey: TKey; ADisposeNode: boolean = true): TTreePair; overload;
     function ExtractPair(constref AKey: TKey; ADisposeNode: boolean = true): TTreePair; overload;
     function ExtractPair(constref ANode: PNode; ADispose: boolean = true): TTreePair; overload;
     function ExtractPair(constref ANode: PNode; ADispose: boolean = true): TTreePair; overload;
@@ -764,6 +782,7 @@ type
     function FindHighest: PNode;
     function FindHighest: PNode;
 
 
     property Count: SizeInt read FCount;
     property Count: SizeInt read FCount;
+
     property Root: PNode read FRoot;
     property Root: PNode read FRoot;
     function Find(constref AKey: TKey): PNode;
     function Find(constref AKey: TKey): PNode;
     function ContainsKey(constref AKey: TKey; out ANode: PNode): boolean; overload; inline;
     function ContainsKey(constref AKey: TKey; out ANode: PNode): boolean; overload; inline;
@@ -785,8 +804,6 @@ type
   end;
   end;
 
 
   TAVLTreeMap<TKey, TValue> = class(TCustomAVLTreeMap<TKey, TValue, TEmptyRecord>)
   TAVLTreeMap<TKey, TValue> = class(TCustomAVLTreeMap<TKey, TValue, TEmptyRecord>)
-  protected
-    function AddNode: PNode; override;
   end;
   end;
 
 
   TIndexedAVLTreeMap<TKey, TValue> = class(TCustomAVLTreeMap<TKey, TValue, SizeInt>)
   TIndexedAVLTreeMap<TKey, TValue> = class(TCustomAVLTreeMap<TKey, TValue, SizeInt>)
@@ -801,8 +818,6 @@ type
 
 
     procedure NodeAdded(ANode: PNode); override;
     procedure NodeAdded(ANode: PNode); override;
     procedure DeletingNode(ANode: PNode; AOrigin: boolean); override;
     procedure DeletingNode(ANode: PNode; AOrigin: boolean); override;
-
-    function AddNode: PNode; override;
   public
   public
     function GetNodeAtIndex(AIndex: SizeInt): PNode;
     function GetNodeAtIndex(AIndex: SizeInt): PNode;
     function NodeToIndex(ANode: PNode): SizeInt;
     function NodeToIndex(ANode: PNode): SizeInt;
@@ -818,7 +833,9 @@ type
   public type
   public type
     TItemEnumerator = TKeyEnumerator;
     TItemEnumerator = TKeyEnumerator;
   public
   public
-    function Add(constref AValue: T): PNode; reintroduce;
+    function Add(constref AValue: T): PNode; reintroduce; inline;
+    function AddNode(ANode: PNode): boolean; reintroduce; inline;
+
     property OnNotify: TCollectionNotifyEvent<T> read FOnKeyNotify write FOnKeyNotify;
     property OnNotify: TCollectionNotifyEvent<T> read FOnKeyNotify write FOnKeyNotify;
   end;
   end;
 
 
@@ -829,7 +846,9 @@ type
   public type
   public type
     TItemEnumerator = TKeyEnumerator;
     TItemEnumerator = TKeyEnumerator;
   public
   public
-    function Add(constref AValue: T): PNode; reintroduce;
+    function Add(constref AValue: T): PNode; reintroduce; inline;
+    function AddNode(ANode: PNode): boolean; reintroduce; inline;
+
     property OnNotify: TCollectionNotifyEvent<T> read FOnKeyNotify write FOnKeyNotify;
     property OnNotify: TCollectionNotifyEvent<T> read FOnKeyNotify write FOnKeyNotify;
   end;
   end;
 
 
@@ -858,6 +877,8 @@ type
   protected
   protected
     function GetPtrEnumerator: TEnumerator<PT>; override;
     function GetPtrEnumerator: TEnumerator<PT>; override;
     function GetCount: SizeInt; override;
     function GetCount: SizeInt; override;
+    function GetCapacity: SizeInt; override;
+    procedure SetCapacity(AValue: SizeInt); override;
     function GetOnNotify: TCollectionNotifyEvent<T>; override;
     function GetOnNotify: TCollectionNotifyEvent<T>; override;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); override;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); override;
   public
   public
@@ -871,6 +892,8 @@ type
     function Extract(constref AValue: T): T; override;
     function Extract(constref AValue: T): T; override;
     procedure Clear; override;
     procedure Clear; override;
     function Contains(constref AValue: T): Boolean; override;
     function Contains(constref AValue: T): Boolean; override;
+
+    procedure TrimExcess; override;
   end;
   end;
 
 
   TSortedHashSet<T> = class(TCustomSet<T>)
   TSortedHashSet<T> = class(TCustomSet<T>)
@@ -881,6 +904,8 @@ type
     FInternalTree: TAVLTree<T>;
     FInternalTree: TAVLTree<T>;
     function DoGetEnumerator: TEnumerator<T>; override;
     function DoGetEnumerator: TEnumerator<T>; override;
     function GetCount: SizeInt; override;
     function GetCount: SizeInt; override;
+    function GetCapacity: SizeInt; override;
+    procedure SetCapacity(AValue: SizeInt); override;
     function GetOnNotify: TCollectionNotifyEvent<T>; override;
     function GetOnNotify: TCollectionNotifyEvent<T>; override;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); override;
     procedure SetOnNotify(AValue: TCollectionNotifyEvent<T>); override;
   protected type
   protected type
@@ -927,6 +952,8 @@ type
     function Extract(constref AValue: T): T; override;
     function Extract(constref AValue: T): T; override;
     procedure Clear; override;
     procedure Clear; override;
     function Contains(constref AValue: T): Boolean; override;
     function Contains(constref AValue: T): Boolean; override;
+
+    procedure TrimExcess; override;
   end;
   end;
 
 
 function InCircularRange(ABottom, AItem, ATop: SizeInt): Boolean;
 function InCircularRange(ABottom, AItem, ATop: SizeInt): Boolean;
@@ -2496,6 +2523,16 @@ begin
   Result := FInternalDictionary.Count;
   Result := FInternalDictionary.Count;
 end;
 end;
 
 
+function THashSet<T>.GetCapacity: SizeInt;
+begin
+  Result := FInternalDictionary.Capacity;
+end;
+
+procedure THashSet<T>.SetCapacity(AValue: SizeInt);
+begin
+  FInternalDictionary.Capacity := AValue;
+end;
+
 function THashSet<T>.GetOnNotify: TCollectionNotifyEvent<T>;
 function THashSet<T>.GetOnNotify: TCollectionNotifyEvent<T>;
 begin
 begin
   Result := FInternalDictionary.OnKeyNotify;
   Result := FInternalDictionary.OnKeyNotify;
@@ -2569,6 +2606,11 @@ begin
   Result := FInternalDictionary.ContainsKey(AValue);
   Result := FInternalDictionary.ContainsKey(AValue);
 end;
 end;
 
 
+procedure THashSet<T>.TrimExcess;
+begin
+  FInternalDictionary.TrimExcess;
+end;
+
 { TAVLTreeNode<TREE_CONSTRAINTS, TTree> }
 { TAVLTreeNode<TREE_CONSTRAINTS, TTree> }
 
 
 function TAVLTreeNode<TREE_CONSTRAINTS, TTree>.Successor: PNode;
 function TAVLTreeNode<TREE_CONSTRAINTS, TTree>.Successor: PNode;
@@ -3137,6 +3179,40 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TCustomAVLTreeMap<TREE_CONSTRAINTS>.InternalAdd(ANode: PNode; ADispisable: boolean): PNode;
+var
+  LParent: PNode;
+begin
+  Result := ANode;
+  case FindInsertNode(ANode, LParent) of
+    -1: LParent.Left := ANode;
+    0:
+      if Assigned(LParent) then
+        case FDuplicates of
+          dupAccept: LParent.Right := ANode;
+          dupIgnore:
+            begin
+              LParent.Right := nil;
+              if ADispisable then
+                Dispose(ANode);
+              Exit(LParent);
+            end;
+          dupError:
+            begin
+              LParent.Right := nil;
+              if ADispisable then
+                Dispose(ANode);
+              Result := nil;
+              raise EListError.Create(SCollectionDuplicate);
+            end;
+        end;
+    1: LParent.Right := ANode;
+  end;
+
+  InternalAdd(ANode, LParent);
+  NodeNotify(ANode, cnAdded, false);
+end;
+
 procedure TCustomAVLTreeMap<TREE_CONSTRAINTS>.InternalDelete(ANode: PNode);
 procedure TCustomAVLTreeMap<TREE_CONSTRAINTS>.InternalDelete(ANode: PNode);
 var
 var
   t, y, z: PNode;
   t, y, z: PNode;
@@ -3272,46 +3348,74 @@ begin
   FComparer := AComparer;
   FComparer := AComparer;
 end;
 end;
 
 
+function TCustomAVLTreeMap<TREE_CONSTRAINTS>.NewNode: PNode;
+begin
+  Result := AllocMem(SizeOf(TNode));
+  Initialize(Result^);
+end;
+
+function TCustomAVLTreeMap<TREE_CONSTRAINTS>.NewNodeArray(ACount: SizeInt): PNode;
+begin
+  Result := AllocMem(ACount * SizeOf(TNode));
+  Initialize(Result^, ACount);
+end;
+
+procedure TCustomAVLTreeMap<TREE_CONSTRAINTS>.NewNodeArray(out AArray: TArray<PNode>; ACount: SizeInt);
+var
+  i: Integer;
+begin
+  SetLength(AArray, ACount);
+  for i := 0 to ACount-1 do
+    AArray[i] := NewNode;
+end;
+
+procedure TCustomAVLTreeMap<TREE_CONSTRAINTS>.DisposeNode(ANode: PNode);
+begin
+  Dispose(ANode);
+end;
+
+procedure TCustomAVLTreeMap<TREE_CONSTRAINTS>.DisposeNodeArray(ANode: PNode; ACount: SizeInt);
+begin
+  Finalize(ANode^, ACount);
+  FreeMem(ANode);
+end;
+
+procedure TCustomAVLTreeMap<TREE_CONSTRAINTS>.DisposeNodeArray(var AArray: TArray<PNode>);
+var
+  i: Integer;
+begin
+  for i := 0 to High(AArray) do
+    Dispose(AArray[i]);
+  AArray := nil;
+end;
+
 destructor TCustomAVLTreeMap<TREE_CONSTRAINTS>.Destroy;
 destructor TCustomAVLTreeMap<TREE_CONSTRAINTS>.Destroy;
 begin
 begin
+  FKeys.Free;
+  FValues.Free;
   FNodes.Free;
   FNodes.Free;
   Clear;
   Clear;
 end;
 end;
 
 
+function TCustomAVLTreeMap<TREE_CONSTRAINTS>.AddNode(ANode: PNode): boolean;
+begin
+  Result := ANode=InternalAdd(ANode, false);
+end;
+
+function TCustomAVLTreeMap<TREE_CONSTRAINTS>.Add(constref APair: TTreePair): PNode;
+begin
+  Result := NewNode;
+  Result.Data.Key := APair.Key;
+  Result.Data.Value := APair.Value;
+  Result := InternalAdd(Result, true);
+end;
+
 function TCustomAVLTreeMap<TREE_CONSTRAINTS>.Add(constref AKey: TKey; constref AValue: TValue): PNode;
 function TCustomAVLTreeMap<TREE_CONSTRAINTS>.Add(constref AKey: TKey; constref AValue: TValue): PNode;
-var
-  LParent: PNode;
 begin
 begin
-  Result := AddNode;
+  Result := NewNode;
   Result.Data.Key := AKey;
   Result.Data.Key := AKey;
   Result.Data.Value := AValue;
   Result.Data.Value := AValue;
-
-  // insert new node
-  case FindInsertNode(Result, LParent) of
-    -1: LParent.Left := Result;
-    0:
-      if Assigned(LParent) then
-        case FDuplicates of
-          dupAccept: LParent.Right := Result;
-          dupIgnore:
-            begin
-              LParent.Right := nil;
-              Dispose(Result);
-              Exit(LParent);
-            end;
-          dupError:
-            begin
-              LParent.Right := nil;
-              Dispose(Result);
-              Result := nil;
-              raise EListError.Create(SCollectionDuplicate);
-            end;
-        end;
-    1: LParent.Right := Result;
-  end;
-
-  InternalAdd(Result, LParent);
-  NodeNotify(Result, cnAdded, false);
+  Result := InternalAdd(Result, true);
 end;
 end;
 
 
 function TCustomAVLTreeMap<TREE_CONSTRAINTS>.Remove(constref AKey: TKey; ADisposeNode: boolean): boolean;
 function TCustomAVLTreeMap<TREE_CONSTRAINTS>.Remove(constref AKey: TKey; ADisposeNode: boolean): boolean;
@@ -3506,14 +3610,6 @@ begin
   end;
   end;
 end;
 end;
 
 
-{ TAVLTreeMap<TKey, TValue> }
-
-function TAVLTreeMap<TKey, TValue>.AddNode: PNode;
-begin
-  Result := New(PNode);
-  Result^ := Default(TNode);
-end;
-
 { TIndexedAVLTreeMap<TKey, TValue> }
 { TIndexedAVLTreeMap<TKey, TValue> }
 
 
 procedure TIndexedAVLTreeMap<TKey, TValue>.RotateRightRight(ANode: PNode);
 procedure TIndexedAVLTreeMap<TKey, TValue>.RotateRightRight(ANode: PNode);
@@ -3588,12 +3684,6 @@ begin
   until false;
   until false;
 end;
 end;
 
 
-function TIndexedAVLTreeMap<TKey, TValue>.AddNode: PNode;
-begin
-  Result := PNode(New(PNode));
-  Result^ := Default(TNode);
-end;
-
 function TIndexedAVLTreeMap<TKey, TValue>.GetNodeAtIndex(AIndex: SizeInt): PNode;
 function TIndexedAVLTreeMap<TKey, TValue>.GetNodeAtIndex(AIndex: SizeInt): PNode;
 begin
 begin
   if (AIndex<0) or (AIndex>=Count) then
   if (AIndex<0) or (AIndex>=Count) then
@@ -3701,6 +3791,11 @@ begin
   Result := inherited Add(AValue, EmptyRecord);
   Result := inherited Add(AValue, EmptyRecord);
 end;
 end;
 
 
+function TAVLTree<T>.AddNode(ANode: PNode): boolean;
+begin
+  Result := inherited AddNode(ANode);
+end;
+
 { TIndexedAVLTree<T> }
 { TIndexedAVLTree<T> }
 
 
 function TIndexedAVLTree<T>.Add(constref AValue: T): PNode;
 function TIndexedAVLTree<T>.Add(constref AValue: T): PNode;
@@ -3708,6 +3803,11 @@ begin
   Result := inherited Add(AValue, EmptyRecord);
   Result := inherited Add(AValue, EmptyRecord);
 end;
 end;
 
 
+function TIndexedAVLTree<T>.AddNode(ANode: PNode): boolean;
+begin
+  Result := inherited AddNode(ANode);
+end;
+
 { TSortedSet<T>.TSortedSetEnumerator }
 { TSortedSet<T>.TSortedSetEnumerator }
 
 
 function TSortedSet<T>.TSortedSetEnumerator.GetCurrent: T;
 function TSortedSet<T>.TSortedSetEnumerator.GetCurrent: T;
@@ -3754,6 +3854,15 @@ begin
   Result := FInternalTree.Count;
   Result := FInternalTree.Count;
 end;
 end;
 
 
+function TSortedSet<T>.GetCapacity: SizeInt;
+begin
+  Result := FInternalTree.Count;
+end;
+
+procedure TSortedSet<T>.SetCapacity(AValue: SizeInt);
+begin
+end;
+
 function TSortedSet<T>.GetOnNotify: TCollectionNotifyEvent<T>;
 function TSortedSet<T>.GetOnNotify: TCollectionNotifyEvent<T>;
 begin
 begin
   Result := FInternalTree.OnKeyNotify;
   Result := FInternalTree.OnKeyNotify;
@@ -3802,7 +3911,7 @@ begin
   if not Result then
   if not Result then
     Exit;
     Exit;
 
 
-  LNodePtr := FInternalTree.AddNode;
+  LNodePtr := FInternalTree.NewNode;
   LNodePtr^.Data.Key := AValue;
   LNodePtr^.Data.Key := AValue;
 
 
   case LCompare of
   case LCompare of
@@ -3845,6 +3954,10 @@ begin
   Result := FInternalTree.ContainsKey(AValue);
   Result := FInternalTree.ContainsKey(AValue);
 end;
 end;
 
 
+procedure TSortedSet<T>.TrimExcess;
+begin
+end;
+
 { TSortedHashSet<T>.TSortedHashSetEqualityComparer }
 { TSortedHashSet<T>.TSortedHashSetEqualityComparer }
 
 
 function TSortedHashSet<T>.TSortedHashSetEqualityComparer.Equals(constref ALeft, ARight: PT): Boolean;
 function TSortedHashSet<T>.TSortedHashSetEqualityComparer.Equals(constref ALeft, ARight: PT): Boolean;
@@ -3928,6 +4041,16 @@ begin
   Result := FInternalDictionary.Count;
   Result := FInternalDictionary.Count;
 end;
 end;
 
 
+function TSortedHashSet<T>.GetCapacity: SizeInt;
+begin
+  Result := FInternalDictionary.Capacity;
+end;
+
+procedure TSortedHashSet<T>.SetCapacity(AValue: SizeInt);
+begin
+  FInternalDictionary.Capacity := AValue;
+end;
+
 function TSortedHashSet<T>.GetOnNotify: TCollectionNotifyEvent<T>;
 function TSortedHashSet<T>.GetOnNotify: TCollectionNotifyEvent<T>;
 begin
 begin
   Result := FInternalTree.OnKeyNotify;
   Result := FInternalTree.OnKeyNotify;
@@ -4027,4 +4150,9 @@ begin
   inherited;
   inherited;
 end;
 end;
 
 
+procedure TSortedHashSet<T>.TrimExcess;
+begin
+  FInternalDictionary.TrimExcess;
+end;
+
 end.
 end.

+ 2 - 0
Units/Utils/generics.dictionaries.inc

@@ -1995,6 +1995,8 @@ end;
 procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TrimExcess;
 procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.TrimExcess;
 begin
 begin
   SetCapacity(Succ(FItemsLength));
   SetCapacity(Succ(FItemsLength));
+  FQueue.TrimExcess;
+  FQueue.FIdx.TrimExcess;
 end;
 end;
 
 
 procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.SetItem(constref AValue: TValue;
 procedure TDeamortizedDArrayCuckooMap<CUCKOO_CONSTRAINTS>.SetItem(constref AValue: TValue;

Some files were not shown because too many files changed in this diff