Browse Source

Refactor: improve datasource/grid pattern significantly.

Herman Schoenfeld 7 years ago
parent
commit
fc78e42918

+ 59 - 173
src/core/UDataSources.pas

@@ -19,7 +19,6 @@ type
     public
       function GetEntityKey(constref AItem: TAccount) : Variant; override;
       function GetItemField(constref AItem: TAccount; const ABindingName : AnsiString) : Variant; override;
-      procedure DehydrateItem(constref AItem: TAccount; var ATableRow: Variant); override;
   end;
 
   { TAccountsDataSource }
@@ -63,7 +62,6 @@ type
       property EndBlock : Cardinal read FEnd write FEnd;
       function GetEntityKey(constref AItem: TOperationResume) : Variant; override;
       function GetItemField(constref AItem: TOperationResume; const ABindingName : AnsiString) : Variant; override;
-      procedure DehydrateItem(constref AItem: TOperationResume; var ATableRow: Variant); override;
   end;
 
   { TAccountsOperationsDataSource }
@@ -94,14 +92,6 @@ type
       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
@@ -118,11 +108,15 @@ function TAccountsDataSourceBase.GetColumns : TDataColumns;
 begin
   Result := TDataColumns.Create(
     TDataColumn.From('Account'),
+    TDataColumn.From('AccountNumber'),
     TDataColumn.From('Name'),
     TDataColumn.From('Balance'),
+    TDataColumn.From('BalanceDecimal'),
     TDataColumn.From('Key'),
+    TDataColumn.From('Type'),
     TDataColumn.From('State'),
     TDataColumn.From('Price'),
+    TDataColumn.From('PriceDecimal'),
     TDataColumn.From('LockedUntil')
   );
 end;
@@ -137,44 +131,30 @@ var
   index : Integer;
 begin
    if ABindingName = 'Account' then
+     Result := TAccountComp.AccountNumberToAccountTxtNumber(AItem.account)
+   else if ABindingName = 'AccountNumber' then
      Result := AItem.account
    else if ABindingName = 'Name' then
      Result := AItem.name
    else if ABindingName = 'Balance' then
+     Result := AItem.Balance
+   else if ABindingName = 'BalanceDecimal' then
      Result := TAccountComp.FormatMoneyDecimal(AItem.Balance)
-{   else if ABindingName = 'Key' then begin
-     index := TWallet.Keys.AccountsKeyList.IndexOfAccountKey(AItem.accountInfo.accountKey);
-     if index>=0 then
-        Result := TWallet.Keys[index].Name
-     else
-         Result := TAccountComp.AccountPublicKeyExport(AItem.accountInfo.accountKey); }
    else if ABindingName = 'Key' then
      Result := TAccountComp.AccountPublicKeyExport(AItem.accountInfo.accountKey)
-   else if ABindingName = 'AccType' then
+   else if ABindingName = 'Type' then
      Result := AItem.account_type
    else if ABindingName = 'State' then
      Result := AItem.accountInfo.state
    else if ABindingName = 'Price' then
+     Result := AItem.accountInfo.price
+   else if ABindingName = 'PriceDecimal' then
      Result := TAccountComp.FormatMoneyDecimal(AItem.accountInfo.price)
    else if ABindingName = 'LockedUntil' then
      Result := AItem.accountInfo.locked_until_block
    else raise Exception.Create(Format('Field not found "%s"', [ABindingName]));
 end;
 
-procedure TAccountsDataSourceBase.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);
-  ATableRow.Key := 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 := LongWord(AItem.accountInfo.locked_until_block);
-end;
 
 { TAccountsDataSource }
 
@@ -286,13 +266,22 @@ end;
 function TOperationsDataSourceBase.GetColumns : TDataColumns;
 begin
   Result := TDataColumns.Create(
+    TDataColumn.From('UnixTime'),
     TDataColumn.From('Time'),
     TDataColumn.From('Block'),
+    TDataColumn.From('Index'),
+    TDataColumn.From('BlockLocation'),
+    TDataColumn.From('BlockLocationSortable'),
     TDataColumn.From('Account'),
+    TDataColumn.From('AccountNumber'),
     TDataColumn.From('Type'),
+    TDataColumn.From('SubType'),
     TDataColumn.From('Amount'),
+    TDataColumn.From('AmountDecimal'),
     TDataColumn.From('Fee'),
+    TDataColumn.From('FeeDecimal'),
     TDataColumn.From('Balance'),
+    TDataColumn.From('BalanceDecimal'),
     TDataColumn.From('Payload'),
     TDataColumn.From('OPHASH'),
     TDataColumn.From('Description')
@@ -311,90 +300,45 @@ function TOperationsDataSourceBase.GetItemField(constref AItem: TOperationResume
 var
   index : Integer;
 begin
-   if ABindingName = 'Time' then
-     Result := AItem.Time
-   else if ABindingName = 'Block' then
-     Result := UInt64(AItem.Block) * 4294967296 + UInt32(AItem.NOpInsideBlock)   // number pattern = [block][opindex]
-   else if ABindingName = 'Account' then
-     Result := AItem.AffectedAccount
-   else if ABindingName = 'Type' then
-     Result := AItem.OpSubtype
-   else if ABindingName = 'Amount' then
-     Result := TAccountComp.FormatMoneyDecimal(AItem.Amount)
-   else if ABindingName = 'Fee' then
-     Result := TAccountComp.FormatMoneyDecimal(AItem.Fee)
-   else if ABindingName = 'Balance' then
-     Result := TAccountComp.FormatMoneyDecimal(AItem.Balance)
-   else if ABindingName = 'Payload' then
-     Result := AItem.PrintablePayload
-   else if ABindingName = 'OPHASH' then
-     Result := TPCOperation.OperationHashAsHexa(AItem.OperationHash)
-   else if ABindingName = 'Description' then
-     Result :=  AItem.OperationTxt
-   else raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
-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 := AItem.Amount;
-
-  // 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.OperationHashAsHexa(AItem.OperationHash) )
-  else
-    ATableRow.OPHASH := 'None';
-
-  // Description
-  ATableRow.Description := Variant(AItem.OperationTxt);
-
+  if ABindingName = 'UnixTime' then
+    Result := AItem.Time
+  else if ABindingName = 'Time' then
+    Result := UnixTimeToLocalStr(AItem.time)
+  else if ABindingName = 'Block' then
+    Result := AItem.Block
+  else if ABindingName = 'Index' then
+    Result := AItem.NOpInsideBlock
+  else if ABindingName = 'BlockLocation' then
+    Result := IIF(AItem.OpType <> CT_PseudoOp_Reward, Inttostr(AItem.Block) + '/' + Inttostr(AItem.NOpInsideBlock+1), Inttostr(AItem.Block))
+  else if ABindingName = 'BlockLocationSortable' then
+    Result := UInt64(AItem.Block) * 4294967296 + UInt32(AItem.NOpInsideBlock)   // number pattern = [block][opindex]
+  else if ABindingName = 'Account' then
+    Result := TAccountComp.AccountNumberToAccountTxtNumber(AItem.AffectedAccount)
+  else if ABindingName = 'AccountNumber' then
+    Result := AItem.AffectedAccount
+  else if ABindingName = 'Type' then
+    Result := AItem.OpType
+  else if ABindingName = 'SubType' then
+    Result := AItem.OpSubtype
+  else if ABindingName = 'Amount' then
+    Result := AItem.Amount
+  else if ABindingName = 'AmountDecimal' then
+    Result := TAccountComp.FormatMoneyDecimal(AItem.Amount)
+  else if ABindingName = 'Fee' then
+    Result := AItem.Fee
+  else if ABindingName = 'FeeDecimal' then
+    Result := TAccountComp.FormatMoneyDecimal(AItem.Fee)
+  else if ABindingName = 'Balance' then
+    Result := AItem.Balance
+  else if ABindingName = 'BalanceDecimal' then
+    Result := TAccountComp.FormatMoneyDecimal(AItem.Balance)
+  else if ABindingName = 'Payload' then
+    Result := AItem.PrintablePayload
+  else if ABindingName = 'OPHASH' then
+    Result := TPCOperation.OperationHashAsHexa(AItem.OperationHash)
+  else if ABindingName = 'Description' then
+    Result := AItem.OperationTxt
+  else raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
 end;
 
 { TAccountsOperationsDataSource }
@@ -490,7 +434,6 @@ begin
   end;
 end;
 
-
 { TOperationsDataSource }
 
 procedure TOperationsDataSource.FetchAll(const AContainer : TList<TOperationResume>);
@@ -522,62 +465,5 @@ begin
   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.
 

+ 41 - 29
src/gui/UCTRLWallet.pas

@@ -74,8 +74,6 @@ type
     procedure OnAccountsUpdated(Sender: TObject);
     procedure OnAccountsSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
     procedure OnOperationSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
-    procedure OnAccountsGridColumnInitialize(Sender: TObject; AColumn: TVisualColumn);
-    procedure OnOperationsGridColumnInitialize(Sender: TObject; AColumn: TVisualColumn);
     procedure OnPrepareAccountPopupMenu(Sender: TObject; constref ASelection: TVisualGridSelection; out APopupMenu: TPopupMenu);
     procedure OnPrepareOperationsPopupMenu(Sender: TObject; constref ASelection: TVisualGridSelection; out APopupMenu: TPopupMenu);
   public
@@ -119,20 +117,30 @@ begin
   FAccountsGrid.DeselectionType := dtDefault;
   FAccountsGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
   with FAccountsGrid.AddColumn('Account') do begin
+    Binding := 'AccountNumber';
+    SortBinding := 'AccountNumber';
+    DisplayBinding := 'Account';
     Width := 100;
-    Renderer := TCellRenderers.AccountNo;
+    HeaderFontStyles := [fsBold];
+    DataFontStyles := [fsBold];
+    Filters:=SORTABLE_NUMERIC_FILTER;
   end;
   with FAccountsGrid.AddColumn('Name') do begin
     StretchedToFill := true;
-    Renderer := TCellRenderers.AccountName;
+    HeaderAlignment := taCenter;
+    Filters:=SORTABLE_TEXT_FILTER;
   end;
   with FAccountsGrid.AddColumn('Balance') do begin
+    Binding := 'BalanceDecimal';
+    SortBinding := 'Balance';
+    DisplayBinding := 'Balance';
     Width := 100;
-    StretchedToFill := true;
-    Renderer := TCellRenderers.PASCBalance;
+    HeaderAlignment:=taRightJustify;
+    DataAlignment:=taRightJustify;
+    Renderer := TCellRenderers.PASC;
+    Filters:=SORTABLE_NUMERIC_FILTER;
   end;
 
-  FAccountsGrid.OnColumnInitialize := OnAccountsGridColumnInitialize;
   FAccountsGrid.OnSelection := OnAccountsSelected;
   FAccountsGrid.OnFinishedUpdating := OnAccountsUpdated;
   FAccountsGrid.OnPreparePopupMenu := OnPrepareAccountPopupMenu;
@@ -145,35 +153,56 @@ begin
   FOperationsGrid.DeselectionType := dtDefault;
   FOperationsGrid.Options := [vgoColAutoFill, vgoColSizing, vgoSortDirectionAllowNone, vgoAutoHidePaging];
   with FOperationsGrid.AddColumn('Time') do begin
+    SortBinding := 'UnixTime';
+    DisplayBinding := 'UnixTime';
+    Renderer:=TCellRenderers.OperationTime;
     Width := 130;
-    Renderer := TCellRenderers.Date_YYYYMMDDHHMMSS;
     Filters := SORTABLE_NUMERIC_FILTER;
   end;
   with FOperationsGrid.AddColumn('Block') do begin
+    Binding := 'BlockLocation';
+    SortBinding := 'BlockLocationSortable';
     AutoWidth := true;
     Filters := SORTABLE_TEXT_FILTER;
   end;
   with FOperationsGrid.AddColumn('Account') do begin
+    Binding := 'AccountNumber';
+    DisplayBinding := 'Account';
     Width := 100;
     Filters := SORTABLE_NUMERIC_FILTER;
   end;
   with FOperationsGrid.AddColumn('Type') do begin
+    Sanitizer := TCellRenderers.OperationTypeSanitizer;
     Width := 150;
     Filters := SORTABLE_NUMERIC_FILTER;
   end;
   with FOperationsGrid.AddColumn('Amount') do begin
+    Binding := 'AmountDecimal';
+    SortBinding := 'Amount';
+    DisplayBinding := 'Amount';
     Width := 150;
-    Renderer := TCellRenderers.PASCTransfer;
+    HeaderAlignment := taRightJustify;
+    Renderer := TCellRenderers.PASC;
     Filters := SORTABLE_NUMERIC_FILTER;
   end;
   with FOperationsGrid.AddColumn('Fee') do begin
+    Binding := 'FeeDecimal';
+    SortBinding := 'Fee';
+    DisplayBinding := 'Fee';
     AutoWidth := true;
-    Renderer := TCellRenderers.PASCTransfer;
+    HeaderAlignment:=taRightJustify;
+    DataAlignment:=taRightJustify;
+    Renderer := TCellRenderers.PASC;
     Filters := SORTABLE_NUMERIC_FILTER;
   end;
   with FOperationsGrid.AddColumn('Balance') do begin
+    Binding := 'BalanceDecimal';
+    SortBinding := 'Balance';
+    DisplayBinding := 'Balance';
     Width := 100;
-    Renderer := TCellRenderers.PASCBalance;
+    HeaderAlignment:=taRightJustify;
+    DataAlignment:=taRightJustify;
+    Renderer := TCellRenderers.PASC;
     Filters := SORTABLE_NUMERIC_FILTER;
   end;
   with FOperationsGrid.AddColumn('Payload') do begin
@@ -188,10 +217,8 @@ begin
   end;
   with FOperationsGrid.AddColumn('Description') do begin
     StretchedToFill := true;
-    Renderer := TCellRenderers.SmallText;
     Filters := SORTABLE_TEXT_FILTER;
   end;
-  FOperationsGrid.OnColumnInitialize := OnOperationsGridColumnInitialize;
   FOperationsGrid.OnSelection := OnOperationSelected;
   FOperationsGrid.OnPreparePopupMenu := OnPrepareOperationsPopupMenu;
   FOperationsGrid.Caption.Alignment := taCenter;
@@ -303,20 +330,6 @@ begin
   end;
 end;
 
-procedure TCTRLWallet.OnAccountsGridColumnInitialize(Sender: TObject; AColumn: TVisualColumn);
-begin
-  case AColumn.Index of
-    2: AColumn.InternalColumn.Alignment := taRightJustify;
-  end;
-end;
-
-procedure TCTRLWallet.OnOperationsGridColumnInitialize(Sender: TObject; AColumn: TVisualColumn);
-begin
-  case AColumn.Index of
-    4, 5, 6: AColumn.InternalColumn.Alignment := taRightJustify;
-  end;
-end;
-
 procedure TCTRLWallet.SetAccountsMode(AMode: TCTRLWalletAccountsMode);
 var sel1 : TVisualGridSelection; sel2 : TRect;
 begin
@@ -441,8 +454,7 @@ begin
   end;
 end;
 
-procedure TCTRLWallet.OnOperationSelected(Sender: TObject;
-  constref ASelection: TVisualGridSelection);
+procedure TCTRLWallet.OnOperationSelected(Sender: TObject; constref ASelection: TVisualGridSelection);
 var
   row: longint;
   v: variant;

+ 125 - 43
src/gui/UCellRenderers.pas

@@ -19,79 +19,101 @@ type
   { TCellRenderers }
 
   TCellRenderers = class
-    class procedure AccountNo(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-    class procedure AccountName(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-    class procedure Date_YYYYMMDDHHMMSS(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-    class procedure PASCTransfer(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-    class procedure PASCBalance(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+    // Data Sanitizers
+    class function UnixTimeSanitizer(const CellData, RowData: Variant) : Variant;
+    class function OperationTypeSanitizer(const CellData, RowData: Variant) : Variant;
+
+    // Cell Renderers
+    class procedure OperationTime(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+    class procedure PASC(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
     class procedure Payload(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
     class procedure OPHASH(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-    class procedure SmallText(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+
+    // General purpose
+    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
-  SysUtils, UCommon, UAccounts;
+  SysUtils, DateUtils, UCommon, UCommon.Data, UAccounts, UConst;
 
 const
   CT_PASCBALANCE_POS_COL = clGreen;
-  CT_PASCBALANCE_NEU_COL = clGrayText;
+  CT_PASCBALANCE_NEU_COL = clDefault;
   CT_PASCBALANCE_NEG_COL = clRed;
+  CT_PASCBALANCE_0CONF_COL = clBlue;
+
 
 { TCellRenderers }
 
-class procedure TCellRenderers.AccountNo (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-var
- LTextStyle: TTextStyle;
+class function TCellRenderers.UnixTimeSanitizer(const CellData, RowData: Variant) : Variant;
+var uxTime : UInt64;
 begin
- LTextStyle := Canvas.TextStyle;
- LTextStyle.Alignment:=taLeftJustify;
- Canvas.TextStyle:=LTextStyle;
- Canvas.TextRect(Rect, Rect.Left, Rect.Top, CellData, LTextStyle);
- Handled := true;
+  if NOT VarIsNumeric(CellData) then exit;
+  uxTime := CellData;
+  Result := FormatDateTime('yyy-mm-dd hh:nn:ss', UnixToDateTime(uxtime));
 end;
 
-class procedure TCellRenderers.AccountName (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-var
- LTextStyle: TTextStyle;
+class function TCellRenderers.OperationTypeSanitizer(const CellData, RowData: Variant) : Variant;
+var LData : TDataRowData absolute RowData;
+  LType, LSubType : DWord;
 begin
- LTextStyle := Canvas.TextStyle;
- LTextStyle.Alignment:=taLeftJustify;
- Canvas.TextStyle:=LTextStyle;
- Canvas.Font.Bold := true;
- Canvas.TextRect(Rect, Rect.Left, Rect.Top, CellData, LTextStyle);
- Handled := true;
+ LType := LData.Data['Type'];
+ LSubType := LData.Data['SubType'];
+ Result := OperationShortText(LType, LSubType);
 end;
 
-class procedure TCellRenderers.Date_YYYYMMDDHHMMSS (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
-begin
-  Handled := False;
-end;
 
-class procedure TCellRenderers.PASCTransfer (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+class procedure TCellRenderers.OperationTime (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+var
+  LValue : UInt64;
+  LTextStyle: TTextStyle;
+  LRowData : TDataRowData;
+  LStr : AnsiString;
 begin
-  Handled := False;
+  if NOT VarIsNumeric(CellData) then exit;
+  LValue := CellData;
+  if LValue > 0 then
+    LStr := FormatDateTime('yyy-mm-dd hh:nn:ss', UnixToDateTime(LValue))
+  else begin
+    LStr := 'PENDING';
+    Canvas.Font.Color := CT_PASCBALANCE_0CONF_COL;
+  end;
+  Canvas.TextRect(Rect, Rect.Left, Rect.Top, LStr, Canvas.TextStyle);
+  Handled := true;
 end;
 
-class procedure TCellRenderers.PASCBalance (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+class procedure TCellRenderers.PASC (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
 var
-  LNum : Int64;
+  LValue : Int64;
   LTextStyle: TTextStyle;
+  LRowData : TDataRowData;
+  LStr : AnsiString;
 begin
-  if NOT VarIsNumeric(CellData) then
+  if NOT TVariantTool.IsNumeric(CellData) then
     exit;
-  LNum := CellData;
-  Canvas.Font.Color:= IIF (LNum < 0, CT_PASCBALANCE_NEG_COL, IIF(LNum > 0, CT_PASCBALANCE_POS_COL, CT_PASCBALANCE_NEU_COL));
-  Canvas.Font.Style:=[fsBold];
-  LTextStyle := Canvas.TextStyle;
-  LTextStyle.Alignment:=taRightJustify;
-  Canvas.TextRect(Rect, Rect.Left, Rect.Top, TAccountComp.FormatMoney(LNum), LTextStyle);
+  LValue := CellData;
+  LRowData := TDataRowData(RowData);
+
+  if LRowData.HasData('UnixTime')  AND (LRowData['UnixTime'] = 0) then begin
+    Canvas.Font.Color := CT_PASCBALANCE_0CONF_COL;
+    LStr := '('+TAccountComp.FormatMoney(LValue)+')';
+  end else begin
+    Canvas.Font.Color := IIF (LValue < 0, CT_PASCBALANCE_NEG_COL, IIF(LValue > 0, CT_PASCBALANCE_POS_COL, CT_PASCBALANCE_NEU_COL));
+    LStr := TAccountComp.FormatMoney(LValue);
+  end;
+  Canvas.TextRect(Rect, Rect.Left, Rect.Top, LStr, Canvas.TextStyle);
   Handled := true;
 end;
 
 class procedure TCellRenderers.Payload (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
 begin
+  {    s := opr.PrintablePayload;
+  Canvas_TextRect(DrawGrid.Canvas,Rect,s,State,[tfLeft,tfVerticalCenter,tfSingleLine]); }
+
   Handled := False;
 end;
 
@@ -100,10 +122,70 @@ begin
   Handled := False;
 end;
 
-class procedure TCellRenderers.SmallText (Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean);
+
+{ KEY }
+{   else if ABindingName = 'Key' then begin
+     index := TWallet.Keys.AccountsKeyList.IndexOfAccountKey(AItem.accountInfo.accountKey);
+     if index>=0 then
+        Result := TWallet.Keys[index].Name
+     else
+         Result := TAccountComp.AccountPublicKeyExport(AItem.accountInfo.accountKey); }
+
+
+class function TCellRenderers.OperationShortHash(const AOpHash : AnsiString) : AnsiString;
+var
+  len : SizeInt;
 begin
- Handled := False;
+ len := Length(AOpHash);
+  if len > 8 then
+    result := AOpHash.Substring(0, 4) + '...' + AOpHash.Substring(len - 4 - 1, 4)
+  else
+    result := AOpHash;
 end;
 
-end.
+class function TCellRenderers.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 TCellRenderers.AccountKeyShortText(const AText : AnsiString) : AnsiString;
+begin
+ If Length(AText) > 20 then
+   Result := AText.SubString(0, 17) + '...'
+ else
+   Result := AText;
+end;
+
+
+end.

+ 0 - 8
src/gui/wizards/UWIZSendPASC_Confirmation.pas

@@ -52,7 +52,6 @@ type
       property Model : TWIZSendPASCModel read FModel write FModel;
       procedure FetchAll(const AContainer : TList<TAccount>); override;
       function GetItemField(constref AItem: TAccount; const ABindingName : AnsiString) : Variant; override;
-      procedure DehydrateItem(constref AItem: TAccount; var ATableRow: Variant); override;
   end;
 
 { TWIZSendPASC_Confirmation }
@@ -122,13 +121,6 @@ begin
    else raise Exception.Create(Format('Field not found [%s]', [ABindingName]));
 end;
 
-procedure TAccountSenderDataSource.DehydrateItem(constref AItem: TAccount; var ATableRow: Variant);
-begin
-  ATableRow.SenderAccount := TAccountComp.AccountNumberToAccountTxtNumber(AItem.account);
-  ATableRow.Balance := TAccountComp.FormatMoney(AItem.balance);
-  ATableRow.AmountToSend := Model.AmountToSend;
-  ATableRow.Fee := TAccountComp.FormatMoney(Model.DefaultFee);
-end;
 
 procedure TAccountSenderDataSource.FetchAll(const AContainer : TList<TAccount>);
 var

+ 52 - 26
src/libraries/sphere10/UCommon.Data.pas

@@ -77,17 +77,21 @@ const
     class function New(const ADataSourceClassName : AnsiString; const AColumns: TDataColumns): Variant;
   end;
 
-  ETableRow = class(Exception);
+  EDataRow = class(Exception);
 
   TDataRowData = packed record
   public
     vtype: tvartype;
   private
-    vfiller1 : word;
-    vfiller2: int32;
-  public
+    vfiller1 : word; // Variant filler
+    vfiller2: int32; // Variant filler
     vcolumnmap: TDataRow.TColumnMapToIndex;
     vvalues: TArray<Variant>;
+    function GetData(const ABinding : AnsiString): Variant;
+    procedure SetData(const ABinding : AnsiString; const AValue: Variant);
+  public
+    function HasData(const ABinding : AnsiString): boolean;
+    property Data[const ABinding : AnsiString] : Variant read GetData write SetData; default;
   end;
 
   { TColumnFilter }
@@ -182,7 +186,7 @@ const
       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 ABindingName : AnsiString) : Variant; virtual; abstract;
-      procedure DehydrateItem(constref AItem: T; var ADataRow: Variant); virtual; abstract;
+      procedure DehydrateItem(constref AItem: T; var ADataRow: Variant);
       function FetchPage(constref AParams: TPageFetchParams; var ADataTable: TDataTable): TPageFetchResult;
       function GetEntityKey(constref AItem: T) : Variant; virtual;
       procedure OnBeforeFetchAll(constref AParams: TPageFetchParams); virtual;
@@ -231,9 +235,9 @@ const
      class function ConstructRowPredicate(const AFilters : TArray<TColumnFilter>; const ADelegate : TApplyFilterDelegate<T>; const AOperand : TFilterOperand) : IPredicate<T>;
   end;
 
+  { Resources }
 
-{ RESOURCES }
-resourcestring
+ resourcestring
   sAColumnsCantBeNil = 'AColumns can''t be nil!';
   sTooManyValues = 'Too many values';
   sInvalidUTF8String = 'Invalid UTF8 string';
@@ -254,7 +258,7 @@ implementation
 
 uses dateutils;
 
-{ VARIABLES }
+{ Variables }
 
 var
   DataRowType: TDataRow = nil;
@@ -289,24 +293,19 @@ begin
   FColumns.Add(ADataSourceClassName, Result);
 end;
 
-function TDataRow.GetProperty(var Dest: TVarData;
-  const V: TVarData; const Name: string): Boolean;
+function TDataRow.GetProperty(var Dest: TVarData; const V: TVarData; const Name: string): Boolean;
 var
   LRow: TDataRowData absolute V;
 begin
-  Variant(Dest) := LRow.vvalues[LRow.vcolumnmap[Name]];
+  Variant(Dest) := LRow[Name];
   Result := true;
 end;
 
-function TDataRow.SetProperty(var V: TVarData; const Name: string;
-  const Value: TVarData): Boolean;
+function TDataRow.SetProperty(var V: TVarData; const Name: string; const Value: TVarData): Boolean;
 var
   LRow: TDataRowData absolute V;
 begin
-  if NOT LRow.vcolumnmap.ContainsKey(Name) then
-    Exit(true); // TODO: Re-enable this when TVisualColumn added -- ETableRow.Create(Format('TableRow did not have column "%s"', [Name]));
-
-  LRow.vvalues[LRow.vcolumnmap[Name]] := Variant(Value);
+  LRow[Name] := Variant(Value);
   Result := true;
 end;
 
@@ -350,7 +349,7 @@ var
   LColumnMap: TColumnMapToIndex;
 begin
   if not Assigned(AColumns) then
-    raise ETableRow.Create(sAColumnsCantBeNil);
+    raise EDataRow.Create(sAColumnsCantBeNil);
 
   VarClear(Result);
   FillChar(Result, SizeOf(Result), #0);
@@ -363,6 +362,29 @@ begin
   SetLength(TDataRowData(Result).vvalues, LColumnMap.Count);
 end;
 
+{ TDataRowData }
+
+function TDataRowData.HasData(const ABinding : AnsiString): boolean;
+begin
+  Result := Self.vcolumnmap.ContainsKey(ABinding)
+end;
+
+function TDataRowData.GetData(const ABinding: AnsiString): Variant;
+var LIndex : Integer;
+begin
+  if NOT Self.vcolumnmap.TryGetValue(ABinding, LIndex) then
+    raise EDataRow.Create(Format('TDataRowData does not contain column "%s"', [ABinding]));
+  Result := Self.vvalues[LIndex];
+end;
+
+procedure TDataRowData.SetData(const ABinding: AnsiString; const AValue: Variant);
+var LIndex : Integer;
+begin
+  if NOT Self.vcolumnmap.TryGetValue(ABinding, LIndex) then
+    raise EDataRow.Create(Format('TDataRowData does not contain column "%s"', [ABinding]));
+  Self.vvalues[LIndex] := AValue;
+end;
+
 { TCustomDataSource }
 
 constructor TCustomDataSource<T>.Create(AOwner: TComponent);
@@ -372,13 +394,6 @@ begin
   FClassName := Self.ClassName;
 end;
 
-{constructor TCustomDataSource<T>.Create(AOwner: TComponent; const ADataSourceClassName : AnsiString);
-begin
-  inherited Create(AOwner);
-  FLock := TCriticalSection.Create;
-  FClassName := ADataSourceClassName;
-end;}
-
 destructor TCustomDataSource<T>.Destroy;
 var
   i : integer;
@@ -539,6 +554,18 @@ begin
   OnAfterFetchAll(AParams);
 end;
 
+procedure TCustomDataSource<T>.DehydrateItem(constref AItem: T; var ADataRow: Variant);
+var
+  i : integer;
+  LCols : TArray<TDataColumn>;
+  LData : TDataRowData;
+begin
+  LCols := GetColumns;
+  LData := TDataRowData(ADataRow);
+  for i := Low(LCols) to High(LCols) do
+    LData[LCols[i].Name] := GetItemField(AItem, LCols[i].Name);
+end;
+
 procedure TCustomDataSource<T>.OnBeforeFetchAll(constref AParams: TPageFetchParams);
 begin
 end;
@@ -1013,4 +1040,3 @@ initialization
 finalization
   DataRowType.Free;
 end.
-

+ 1 - 1
src/libraries/sphere10/UCommon.UI.pas

@@ -62,7 +62,6 @@ type
     procedure SetImageListPicture(AImageList: TImageList; AIndex : SizeInt);
   end;
 
-
 implementation
 
 {%region TApplicationForm}
@@ -172,6 +171,7 @@ end;
 
 {%endregion}
 
+
 end.
 
 

+ 189 - 74
src/libraries/sphere10/UVisualGrid.pas

@@ -28,6 +28,12 @@ uses
 
  type
 
+   { Forward Decls }
+
+   TCustomVisualGrid = class;
+   TVisualColumn = class;
+
+
   { TSelectionType }
 
   TSelectionType = (stNone, stCell, stRow, stMultiRow);
@@ -56,10 +62,8 @@ uses
     property ColCount: longint read GetColCount;
   end;
 
-  TCustomVisualGrid = class;
-  TVisualColumn = class;
-
   TVisualColumnRenderer = procedure(Sender: TObject; ACol, ARow: Longint; Canvas: TCanvas; Rect: TRect; State: TGridDrawState; const CellData, RowData: Variant; var Handled: boolean) of object;
+  TVisualColumnDataSanitizer = function(const CellData, RowData : Variant) : Variant of object;
 
   { TVisualColumn }
 
@@ -69,38 +73,72 @@ uses
     function GetWidth: Integer; inline;
     procedure SetSortDirection(AValue: TSortDirection);
     procedure SetStretchedToFill(AValue: boolean); inline;
+    procedure SetAutoWidth(AValue: boolean); inline;
     procedure SetWidth(AValue: Integer); inline;
+    procedure SetName(const AValue: utf8string);
+    procedure SetBinding(const AValue : AnsiString);
+    procedure SetHeaderAlignment(const AAlignment : TAlignment);
+    procedure SetHeaderFontStyles(const AStyles : TFontStyles);
+    procedure SetDataAlignment(const AAlignment : TAlignment);
+    procedure SetDataFontStyles(const AStyles : TFontStyles);
   protected
     FColumn: TGridColumn;
     FGrid: TCustomVisualGrid;
     FSortDirection: TSortDirection;
     FIgnoreRefresh: Boolean;
 
+    // Functional
     FIndex : Integer;
     FName : utf8string;
     FFilters : TDataFilters;
     FBinding : AnsiString;
+    FSortBinding: AnsiString;
+    FDisplayBinding: AnsiString;
+    FSanitizer : TVisualColumnDataSanitizer;
     FWidth : Integer;
     FStretchColumn : Boolean;
     FAutoWidth : boolean;
     FRenderer : TVisualColumnRenderer;
     FVisible : boolean; // TODO: implement this functionality
     FSortSequence : Integer; // TODO: implement this functionality
+
+    // Style
+    FHasHeaderAlignment : boolean;
+    FHasHeaderFontStyles : boolean;
+    FHeaderAlignment : TAlignment;
+    FHeaderFontStyles : TFontStyles;
+    FHasDataAlignment : boolean;
+    FHasDataFontStyles : boolean;
+    FDataAlignment : TAlignment;
+    FDataFontStyles : TFontStyles;
+
   public
+    constructor Create(AGrid: TCustomVisualGrid);
     property Index : Integer read FIndex write FIndex;
-    property Name : utf8string read FName write FName;
+    property Name : utf8string read FName write SetName;
     property Filters : TDataFilters read FFilters write FFilters;
-    property Binding : AnsiString read FBinding write FBinding;
-    property AutoWidth : boolean read FAutoWidth write FAutoWidth;
+    property Binding : AnsiString read FBinding write SetBinding;
+    property SortBinding : AnsiString read FSortBinding write FSortBinding;
+    property DisplayBinding : AnsiString read FDisplayBinding write FDisplayBinding;
+    property Sanitizer: TVisualColumnDataSanitizer read FSanitizer write FSanitizer;
+    property AutoWidth : boolean read FAutoWidth write SetAutoWidth;
     property Visible : boolean read FVisible write FVisible;
     property SortSequence : Integer read FSortSequence write FSortSequence;
     property InternalColumn : TGridColumn read FColumn;
-    constructor Create(AGrid: TCustomVisualGrid);
     property StretchedToFill: boolean read GetStretchedToFill write SetStretchedToFill;
     property Width: Integer read GetWidth write SetWidth;
     property SortDirection: TSortDirection read FSortDirection write SetSortDirection;
     property Renderer : TVisualColumnRenderer read FRenderer write FRenderer;
-    public procedure AttachGridColumn(AGridColumn : TGridColumn);
+    property HasHeaderAlignment : boolean read FHasHeaderAlignment;
+    property HasHeaderFontStyles : boolean read FHasHeaderFontStyles;
+    property HeaderAlignment : TAlignment read FHeaderAlignment write SetHeaderAlignment;
+    property HeaderFontStyles : TFontStyles read FHeaderFontStyles write SetHeaderFontStyles;
+    property HasDataAlignment : boolean read FHasDataAlignment;
+    property HasDataFontStyles : boolean read FHasDataFontStyles;
+    property DataAlignment : TAlignment read FDataAlignment write SetDataAlignment;
+    property DataFontStyles : TFontStyles read FDataFontStyles write SetDataFontStyles;
+
+    procedure AttachGridColumn(AGridColumn : TGridColumn);
   end;
 
   { TVisualGrid Events }
@@ -234,12 +272,9 @@ uses
     FMultiSearchEdits: TObjectList<TSearchEdit>;
     FColumns: TObjectList<TVisualColumn>;
   protected { events for UI }
-    procedure StandardDrawCell(Sender: TObject; ACol, ARow: Longint;
-      Rect: TRect; State: TGridDrawState);
-    procedure GridMouseDown(Sender: TObject; Button: TMouseButton;
-      Shift: TShiftState; X, Y: Integer);
-    procedure GridMouseUp(Sender: TObject; Button: TMouseButton;
-      Shift: TShiftState; X, Y: Integer);
+    procedure StandardDrawCell(Sender: TObject; ACol, ARow: Longint; Rect: TRect; State: TGridDrawState);
+    procedure GridMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+    procedure GridMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure SearchKindPopupMenuClick(Sender: TObject);
     procedure GridSelection(Sender: TObject; aCol, aRow: Integer);
     procedure GridHeaderClick(Sender: TObject; IsColumn: Boolean; Index: Integer);
@@ -538,6 +573,29 @@ end;
 
 { TVisualColumn }
 
+constructor TVisualColumn.Create(AGrid: TCustomVisualGrid);
+begin
+  FGrid := AGrid;
+  FColumn := nil;
+  FHasHeaderAlignment:=false;
+  FHasHeaderFontStyles:=false;
+  FHasDataAlignment:=false;
+  FHasDataFontStyles:=false;
+  FIndex := -1;
+  FName := '';
+  FBinding := '';
+  FSortBinding := '';
+  FDisplayBinding := '';
+  FStretchColumn:=false;
+  FAutoWidth:=false;
+  FWidth :=-1;
+  FRenderer := nil;
+  FSanitizer:= nil;
+  FVisible := true;
+  FSortSequence := -1;
+  FFilters :=[];
+end;
+
 function TVisualColumn.GetStretchedToFill: boolean;
 begin
   Result := FColumn.SizePriority > 0;
@@ -561,36 +619,80 @@ procedure TVisualColumn.SetStretchedToFill(AValue: boolean);
 begin
   FStretchColumn:=AValue;
   if Assigned(FColumn) then
-    FColumn.SizePriority := ifthen(AValue, 1);
+    AttachGridColumn(FColumn)
+end;
+
+procedure TVisualColumn.SetAutoWidth(AValue: boolean);
+begin
+  FAutoWidth:=AValue;
+  if Assigned(FColumn) then
+    AttachGridColumn(FColumn)
 end;
 
 procedure TVisualColumn.SetWidth(AValue: Integer);
 begin
-  if Self.AutoWidth then
-    Self.StretchedToFill := false
-  else if Self.StretchedToFill then
-    Self.StretchedToFill := true
-  else begin
-    Self.StretchedToFill := False;
-    if Assigned(FColumn) then
-      FColumn.Width := AValue;
-  end;
   FWidth := AValue;
+  if Assigned(FColumn) then
+    AttachGridColumn(FColumn)
 end;
 
-constructor TVisualColumn.Create(AGrid: TCustomVisualGrid);
+procedure TVisualColumn.SetName(const AValue: utf8string);
 begin
-  FGrid := AGrid;
-  FColumn := nil;
+  FName := AValue;
+  Binding := UTF8Decode(AValue);
+end;
+
+procedure TVisualColumn.SetBinding(const AValue: AnsiString);
+begin
+  FBinding := AValue;
+  FSortBinding := AValue;
+  FDisplayBinding := AValue;
+end;
+
+procedure TVisualColumn.SetHeaderAlignment(const AAlignment : TAlignment);
+begin
+  FHasHeaderAlignment := true;
+  FHeaderAlignment := AAlignment;
+end;
+
+procedure TVisualColumn.SetHeaderFontStyles(const AStyles : TFontStyles);
+begin
+  FHasHeaderFontStyles := true;
+  FHeaderFontStyles := AStyles;
+end;
+
+procedure TVisualColumn.SetDataAlignment(const AAlignment : TAlignment);
+begin
+  FHasDataAlignment := true;
+  FDataAlignment := AAlignment;
+end;
+
+procedure TVisualColumn.SetDataFontStyles(const AStyles : TFontStyles);
+begin
+  FHasDataFontStyles := true;
+  FDataFontStyles := AStyles;
 end;
 
 procedure TVisualColumn.AttachGridColumn(AGridColumn : TGridColumn);
 begin
+  if NOT Assigned(AGridColumn) then raise EArgumentNilException.Create('AGridColumn');
+
+  // Set TDrawGrid column
   Self.FColumn := AGridColumn;
-  Self.AutoWidth := Self.AutoWidth;
-  Self.StretchedToFill := Self.StretchedToFill;
-  Self.Width := Self.Width;
-  Self.FColumn.Title.Caption:=''; // already painted in default drawing event
+
+  // Width
+  if FAutoWidth then
+    FStretchColumn := false
+  else if NOT FStretchColumn then begin
+    FColumn.Width := FWidth;
+  end;
+
+  // StretchedToFill
+  FColumn.SizePriority := ifthen(FStretchColumn, 1);
+
+  // Caption - already painted in default drawing event
+  if Self.FColumn.Title.Caption <> '' then
+     Self.FColumn.Title.Caption:='';
 end;
 
 { TVisualGridSelection }
@@ -1175,8 +1277,7 @@ begin
   inherited;
 end;
 
-procedure TCustomVisualGrid.DoDrawCell(Sender: TObject; ACol, ARow: Longint;
-  Rect: TRect; State: TGridDrawState; const RowData: Variant);
+procedure TCustomVisualGrid.DoDrawCell(Sender: TObject; ACol, ARow: Longint; Rect: TRect; State: TGridDrawState; const RowData: Variant);
 const
   {
     Direction triangle points schema
@@ -1458,10 +1559,10 @@ var
     end;
 
     if Assigned(AColumn) then
-      AddFilter(AColumn.Binding, AColumn.Filters)    // add filter for column only
+      AddFilter(AColumn.SortBinding, AColumn.Filters)    // add filter for column only
     else
       for LColumn in FColumns do
-        AddFilter(LColumn.Binding, LColumn.Filters); // add filter for all columns
+        AddFilter(LColumn.SortBinding, LColumn.Filters); // add filter for all columns
 
     LNewStrFilter := LNewStrFilter + QuotedStr(AExpression) + ',';
   end;
@@ -1659,8 +1760,7 @@ var LBinding : AnsiString; LDataColIndex : Integer; LRow : TDataRowData;
 begin
   LRow := TDataRowData(FDataTable.Rows[ARow]);
   LBinding := FColumns[ACol].Binding;
-  LDataColIndex := LRow.vcolumnmap[LBinding];
-  LRow.vvalues[LDataColIndex] := AValue;
+  LRow[LBinding] := AValue;
   TDrawGridAccess(FDrawGrid).InvalidateCell(ACol, ARow, true);
 end;
 
@@ -1926,15 +2026,10 @@ begin
   Result := TVisualColumn.Create(Self);
   Result.Index := FColumns.Count;
   Result.Name := AName;
-  Result.Binding := UTF8Decode(AName);
   if Result.Index = FDefaultStretchedColumn then
     Result.StretchedToFill := true;
-  Result.FRenderer := nil;
-  Result.FVisible := true;
-  Result.FSortSequence := -1;
-  Result.Filters:=[];
+  Result.AttachGridColumn(FDrawGrid.Columns.Add);
   FColumns.Add(Result);
-  Result.AttachGridColumn(FDrawGrid.Columns.Add);  // attach an underlyinh drawgrid
 end;
 
 procedure TCustomVisualGrid.ReloadColumns;
@@ -2107,10 +2202,10 @@ procedure TCustomVisualGrid.FetchPage(out AResult: TPageFetchResult);
         begin
           // try to find column in existing filters
           // if filter not found we need to create it later
-          if not UpdateFilterSortDirection(FColumns[i].Binding, FColumns[i].SortDirection) then
+          if not UpdateFilterSortDirection(FColumns[i].SortBinding, FColumns[i].SortDirection) then
             LColumnsToAdd.Add(i);
         end else
-          UpdateFilterSortDirection(FColumns[i].Binding, sdNone);
+          UpdateFilterSortDirection(FColumns[i].SortBinding, sdNone);
 
       // add missing filters
       FFilters.Count:=FFilters.Count + LColumnsToAdd.Count;
@@ -2123,7 +2218,7 @@ procedure TCustomVisualGrid.FetchPage(out AResult: TPageFetchResult);
         // create new filter
         LFilter := Default(TColumnFilter);
         LFilter.Filter:=vgfSortable;
-        LFilter.ColumnName:= FColumns[idx].Binding;
+        LFilter.ColumnName:= FColumns[idx].SortBinding;
         LFilter.SortSequence := FColumns[idx].SortSequence;
         LFilter.Sort := FColumns[idx].SortDirection;
         FFilters[i] := LFilter;
@@ -2273,46 +2368,68 @@ begin
     FTopPanelLeft.Width:=0;
 end;
 
-procedure TCustomVisualGrid.StandardDrawCell(Sender: TObject; ACol,
-  ARow: Longint; Rect: TRect; State: TGridDrawState);
+procedure TCustomVisualGrid.StandardDrawCell(Sender: TObject; ACol, ARow: Longint; Rect: TRect; State: TGridDrawState);
 var
   LHandled: boolean;
   LCellData, LRow: Variant;
-  LRowData: TDataRowData;
   LColumn : TVisualColumn;
-  LDataColIndex : Integer;
-
-begin
-  LHandled := False;
+  LStyle : TTextStyle;
 
-  if ColCount = 0 then
-    Exit;
+  procedure DrawHeaderCell;
+  begin
+    // Pre-set user defined styles
+    if LColumn.HasHeaderAlignment then begin
+      LStyle := Canvas.TextStyle;
+      LStyle.Alignment := LColumn.HeaderAlignment;
+      Canvas.TextStyle := LStyle;
+    end;
+    if LColumn.HasHeaderFontStyles then Canvas.Font.Style := LColumn.HeaderFontStyles;
 
-  if ARow = 0 then
-    ResizeSearchEdit(ACol);
+    // Standard draw cell
+    DoDrawCell(Self, ACol, ARow, Rect, State, LColumn.Name);
+  end;
 
-  LColumn := FColumns[ACol];
+  procedure DrawDataCell;
+  begin
+    // Pre-set user defined styles
+    if LColumn.HasDataAlignment then begin
+      LStyle := Canvas.TextStyle;
+      LStyle.Alignment := LColumn.DataAlignment;
+      Canvas.TextStyle := LStyle;
+    end;
+    if LColumn.HasDataFontStyles then Canvas.Font.Style := LColumn.DataFontStyles;
 
-  if (ARow > 0) and Assigned(FDataSource) then begin
+    // Get row/cell data
     LRow := ActiveDataTable^.Rows[ARow-1];
-    LRowData := TDataRowData(LRow);
-    LDataColIndex := LRowData.vcolumnmap[LColumn.Binding];
-    LCellData := LRowData.vvalues[LDataColIndex];
-  end;
+    LCellData := TDataRowData(LRow)[LColumn.DisplayBinding];
 
-  Rect := CalculateCellContentRect(Rect);
+    // Clean data if necessary
+    if Assigned(LColumn.Sanitizer) then
+      LCellData := LColumn.Sanitizer(LCellData, LRow);
 
-  // Try user-settable cell renderer
-  if Assigned(FOnDrawVisualCell) then
-    FOnDrawVisualCell(Self, ACol, ARow, Canvas, Rect, State, LCellData, LRow, LHandled);
+    // Try global cell renderer
+    if Assigned(FOnDrawVisualCell) then
+      FOnDrawVisualCell(Self, ACol, ARow, Canvas, Rect, State, LCellData, LRow, LHandled);
 
-  // Try user-settable column renderer
-  if (NOT LHandled) AND Assigned(LColumn.Renderer) then
+    // Try column renderer
+    if (NOT LHandled) AND Assigned(LColumn.Renderer) then
       LColumn.Renderer(Self, ACol, ARow, Canvas, Rect, State, LCellData, LRow, LHandled);
 
-  // Use default renderer
-  if not LHandled then
-    DoDrawCell(Self, ACol, ARow, Rect, State, LCellData);
+    // Use default renderer
+    if NOT LHandled then
+      DoDrawCell(Self, ACol, ARow, Rect, State, LCellData);
+  end;
+
+begin
+  LHandled := False;
+  if ColCount = 0 then Exit;
+  if ARow = 0 then ResizeSearchEdit(ACol);
+  LColumn := FColumns[ACol];
+  Rect := CalculateCellContentRect(Rect);
+  if ARow = 0 then
+    DrawHeaderCell
+  else if Assigned(FDataSource) then
+    DrawDataCell;
 end;
 
 procedure TCustomVisualGrid.GridMouseDown(Sender: TObject; Button: TMouseButton;
@@ -2462,9 +2579,7 @@ begin
   Result.Bottom := ClipValue(ARect.Bottom - FCellPadding.Bottom - PLATFORM_CELL_RECT_Y_OFFSET, 2, 100000);
 end;
 
-
 initialization
   {$I *.inc}
 
 end.
-