Refactor internal structures for grid/table sorting, prefer TObjectList over Array

This commit is contained in:
Ansgar Becker
2023-01-22 12:49:53 +01:00
parent 9d8c8e7628
commit 7ff9b2e9b7
5 changed files with 170 additions and 134 deletions

View File

@ -18,11 +18,21 @@ uses
type
TOrderCol = class(TObject)
ColumnName: String;
SortDirection: Byte;
TSortItemOrder = (sioAscending, sioDescending);
TSortItem = class(TPersistent)
public
Column: String;
Order: TSortItemOrder;
constructor Create; overload;
constructor Create(lColumn: String; lOrder: TSortItemOrder=sioAscending); overload;
procedure Assign(Source: TPersistent); override;
end;
TSortItems = class(TObjectList<TSortItem>)
public
function ComposeOrderClause: String;
function FindByColumn(Column: String): TSortItem;
procedure Assign(Source: TSortItems);
end;
TOrderColArray = Array of TOrderCol;
TLineBreaks = (lbsNone, lbsWindows, lbsUnix, lbsMac, lbsWide, lbsMixed);
@ -335,7 +345,6 @@ type
procedure FixVT(VT: TVirtualStringTree; MultiLineCount: Word=1);
function GetTextHeight(Font: TFont): Integer;
function ColorAdjustBrightness(Col: TColor; Shift: SmallInt): TColor;
function ComposeOrderClause(Cols: TOrderColArray): String;
procedure DeInitializeVTNodes(Sender: TBaseVirtualTree);
function FindNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode): PVirtualNode;
function SelectNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode=nil): Boolean; overload;
@ -1425,28 +1434,6 @@ begin
end;
{**
Concat all sort options to a ORDER clause
}
function ComposeOrderClause(Cols: TOrderColArray): String;
var
i : Integer;
sort : String;
begin
result := '';
for i := 0 to Length(Cols) - 1 do
begin
if result <> '' then
result := result + ', ';
if Cols[i].SortDirection = ORDER_ASC then
sort := TXT_ASC
else
sort := TXT_DESC;
result := result + MainForm.ActiveConnection.QuoteIdent( Cols[i].ColumnName ) + ' ' + sort;
end;
end;
procedure DeInitializeVTNodes(Sender: TBaseVirtualTree);
var
Node: PVirtualNode;
@ -1817,6 +1804,82 @@ begin
end;
{ *** TSortItem }
constructor TSortItem.Create;
begin
inherited;
Column := '';
Order := sioAscending;
end;
constructor TSortItem.Create(lColumn: String; lOrder: TSortItemOrder=sioAscending);
begin
inherited Create;
Column := lColumn;
Order := lOrder;
end;
procedure TSortItem.Assign(Source: TPersistent);
var
SourceItem: TSortItem;
begin
if Source is TSortItem then begin
SourceItem := Source as TSortItem;
Column := SourceItem.Column;
Order := SourceItem.Order;
end
else
Inherited;
end;
{ *** TSortItems }
function TSortItems.ComposeOrderClause: String;
var
SortItem: TSortItem;
Conn: TDBConnection;
begin
// Concat all sort options to an ORDER BY clause
Result := '';
Conn := MainForm.ActiveConnection;
for SortItem in Self do begin
if Result <> '' then
Result := Result + ', ';
Result := Result + Conn.QuoteIdent(SortItem.Column) +
' ' + IfThen(SortItem.Order = sioAscending, Conn.GetSQLSpecifity(spOrderAsc), Conn.GetSQLSpecifity(spOrderDesc));
end;
end;
function TSortItems.FindByColumn(Column: String): TSortItem;
var
SortItem: TSortItem;
begin
Result := nil;
for SortItem in Self do begin
if SortItem.Column = Column then begin
Result := SortItem;
Break;
end;
end;
end;
procedure TSortItems.Assign(Source: TSortItems);
var
Item, ItemCopy: TSortItem;
begin
Clear;
for Item in Source do begin
ItemCopy := TSortItem.Create;
ItemCopy.Assign(Item);
Add(ItemCopy);
end;
end;
{ *** TDBObjectEditor }

View File

@ -71,12 +71,6 @@ const
{Pebibyte} NAME_PB = ' PiB';
{Exbibyte} NAME_EB = ' EiB';
// Used by ListViews and Grids
ORDER_ASC = 0; // Used for tag-value of "Direction"-button
ORDER_DESC = 1; // dito
TXT_ASC = 'ASC'; // Used for caption of "Direction"-button
TXT_DESC = 'DESC'; // dito
// Data grid: How many bytes to fetch from data fields that are potentially large.
GRIDMAXDATA: Integer = 256;

View File

@ -24,7 +24,7 @@ type
private
{ Private declarations }
FColumnNames: TStringList;
FOrderColumns: TOrderColArray;
FSortItems: TSortItems;
FOldOrderClause: String;
procedure comboColumnsChange(Sender: TObject);
procedure btnOrderClick(Sender: TObject);
@ -53,8 +53,9 @@ begin
FColumnNames.Add(Mainform.SelectedTableColumns[i].Name);
end;
FOrderColumns := Copy(Mainform.DataGridSortColumns, 0, MaxInt);
FOldOrderClause := ComposeOrderClause(FOrderColumns);
FSortItems := TSortItems.Create(True);
FSortItems.Assign(MainForm.DataGridSortItems);
FOldOrderClause := FSortItems.ComposeOrderClause;
// First creation of controls
DisplaySortingControls(Sender);
@ -66,6 +67,7 @@ end;
}
procedure TfrmDataSorting.DisplaySortingControls(Sender: TObject);
var
SortItem: TSortItem;
lblNumber: TLabel;
btnDelete: TButton;
comboColumns: TComboBox;
@ -94,7 +96,8 @@ begin
// Create line with controls for each order column
// TODO: disable repaint on every created control. Sending WM_SETREDRAW=0 message creates artefacts.
LockWindowUpdate(pnlBevel.Handle);
for i:=0 to Length(FOrderColumns)-1 do begin
for i:=0 to FSortItems.Count-1 do begin
SortItem := FSortItems[i];
// 1. Label with number
lblNumber := TLabel.Create(self);
lblNumber.Parent := pnlBevel;
@ -115,7 +118,7 @@ begin
comboColumns.Top := TopPos;
comboColumns.Items.Text := FColumnNames.Text;
comboColumns.Style := csDropDownList; // Not editable
comboColumns.ItemIndex := FColumnNames.IndexOf(FOrderColumns[i].ColumnName);
comboColumns.ItemIndex := FColumnNames.IndexOf(SortItem.Column);
comboColumns.Tag := i+1;
comboColumns.OnChange := comboColumnsChange;
lblNumber.Height := comboColumns.Height;
@ -131,7 +134,7 @@ begin
btnOrder.GroupIndex := i+1; // if > 0 enables Down = True
btnOrder.Glyph.Transparent := True;
btnOrder.Glyph.AlphaFormat := afDefined;
if FOrderColumns[i].SortDirection = ORDER_DESC then begin
if SortItem.Order = sioDescending then begin
MainForm.VirtualImageListMain.GetBitmap(110, btnOrder.Glyph);
btnOrder.Down := True;
end else begin
@ -200,7 +203,7 @@ var
combo : TComboBox;
begin
combo := Sender as TComboBox;
FOrderColumns[combo.Tag-1].ColumnName := combo.Text;
FSortItems[combo.Tag-1].Column := combo.Text;
// Enables OK button
Modified;
@ -216,12 +219,12 @@ var
begin
btn := Sender as TSpeedButton;
btn.Glyph := nil;
if FOrderColumns[btn.Tag-1].SortDirection = ORDER_ASC then begin
if FSortItems[btn.Tag-1].Order = sioAscending then begin
MainForm.VirtualImageListMain.GetBitmap(110, btn.Glyph);
FOrderColumns[btn.Tag-1].SortDirection := ORDER_DESC;
FSortItems[btn.Tag-1].Order := sioDescending;
end else begin
MainForm.VirtualImageListMain.GetBitmap(109, btn.Glyph);
FOrderColumns[btn.Tag-1].SortDirection := ORDER_ASC;
FSortItems[btn.Tag-1].Order := sioAscending;
end;
// Enables OK button
@ -235,24 +238,11 @@ end;
procedure TfrmDataSorting.btnDeleteClick(Sender: TObject);
var
btn: TButton;
i : Integer;
begin
btn := Sender as TButton;
if Length(FOrderColumns)>1 then
begin
// Move remaining items one up
for i := btn.Tag-1 to Length(FOrderColumns) - 2 do
begin
FOrderColumns[i] := FOrderColumns[i+1];
end;
end;
// Delete last item
SetLength(FOrderColumns, Length(FOrderColumns)-1);
FSortItems.Delete(btn.Tag-1);
// Refresh controls
DisplaySortingControls(Sender);
DisplaySortingControls(Self);
// Enables OK button
Modified;
end;
@ -263,30 +253,23 @@ end;
}
procedure TfrmDataSorting.btnAddColClick(Sender: TObject);
var
i, new : Integer;
UnusedColumns: TStringList;
NewSortItem, SortItem: TSortItem;
begin
SetLength(FOrderColumns, Length(FOrderColumns)+1 );
new := Length(FOrderColumns)-1;
FOrderColumns[new] := TOrderCol.Create;
NewSortItem := TSortItem.Create;
FSortItems.Add(NewSortItem);
// Take first unused column as default for new sort column
// Take first unused column as default for new sort item
UnusedColumns := TStringList.Create;
UnusedColumns.AddStrings(FColumnNames);
for i := 0 to Length(FOrderColumns) - 1 do
begin
if UnusedColumns.IndexOf(FOrderColumns[i].ColumnName) > -1 then
begin
UnusedColumns.Delete( UnusedColumns.IndexOf(FOrderColumns[i].ColumnName) );
end;
for SortItem in FSortItems do begin
if UnusedColumns.IndexOf(SortItem.Column) > -1 then
UnusedColumns.Delete(UnusedColumns.IndexOf(SortItem.Column));
end;
if UnusedColumns.Count > 0 then
FOrderColumns[new].ColumnName := UnusedColumns[0]
NewSortItem.Column := UnusedColumns[0]
else
FOrderColumns[new].ColumnName := FColumnNames[0];
// Sort ASC by default
FOrderColumns[new].SortDirection := ORDER_ASC;
NewSortItem.Column := FColumnNames[0];
// Refresh controls
DisplaySortingControls(Sender);
@ -302,7 +285,7 @@ end;
}
procedure TfrmDataSorting.Modified;
begin
btnOk.Enabled := ComposeOrderClause(FOrderColumns) <> FOldOrderClause;
btnOk.Enabled := FSortItems.ComposeOrderClause <> FOldOrderClause;
end;
@ -312,7 +295,7 @@ end;
procedure TfrmDataSorting.btnOKClick(Sender: TObject);
begin
// TODO: apply ordering
Mainform.DataGridSortColumns := FOrderColumns;
MainForm.DataGridSortItems.Assign(FSortItems);
InvalidateVT(Mainform.DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
btnCancel.OnClick(Sender);
end;

View File

@ -426,7 +426,8 @@ type
spISSchemaCol,
spUSEQuery, spKillQuery, spKillProcess,
spFuncLength, spFuncCeil, spFuncLeft, spFuncNow,
spLockedTables, spDisableForeignKeyChecks, spEnableForeignKeyChecks);
spLockedTables, spDisableForeignKeyChecks, spEnableForeignKeyChecks,
spOrderAsc, spOrderDesc);
TDBConnection = class(TComponent)
private
@ -3017,6 +3018,9 @@ begin
[FParameters.Hostname, FParameters.NetTypeName(True), FParameters.Username, UsingPass]
));
FSQLSpecifities[spOrderAsc] := 'ASC';
FSQLSpecifities[spOrderDesc] := 'DESC';
case Parameters.NetTypeGroup of
ngMySQL: begin
FSQLSpecifities[spDatabaseDrop] := 'DROP DATABASE %s';

View File

@ -1211,6 +1211,7 @@ type
FDataGridColumnWidthsCustomized: Boolean;
FDataGridLastClickedColumnHeader: Integer;
FDataGridLastClickedColumnLeftPos: Integer;
FDataGridSortItems: TSortItems;
FSnippetFilenames: TStringList;
FConnections: TDBConnectionList;
FTreeClickHistory: TNodeArray;
@ -1292,7 +1293,6 @@ type
// Data grid related stuff
DataGridHiddenColumns: TStringList;
DataGridSortColumns: TOrderColArray;
DataGridWantedRowCount: Int64;
DataGridTable: TDBObject;
DataGridFocusedCell: TStringList;
@ -1348,6 +1348,7 @@ type
procedure SetupSynEditor(Editor: TSynMemo);
function AnyGridEnsureFullRow(Grid: TVirtualStringTree; Node: PVirtualNode): Boolean;
procedure DataGridEnsureFullRows(Grid: TVirtualStringTree; SelectedOnly: Boolean);
property DataGridSortItems: TSortItems read FDataGridSortItems write FDataGridSortItems;
function GetEncodingByName(Name: String): TEncoding;
function GetEncodingName(Encoding: TEncoding): String;
function GetCharsetByEncoding(Encoding: TEncoding): String;
@ -2050,6 +2051,8 @@ begin
DatatypeCategories[dtcOther].Color := AppSettings.ReadInt(asFieldColorOther);
CalcNullColors;
FDataGridSortItems := TSortItems.Create(True);
DataLocalNumberFormat := AppSettings.ReadBool(asDataLocalNumberFormat);
DataGridTable := nil;
FActiveDbObj := nil;
@ -5724,21 +5727,21 @@ const
NumSortChars: Array of Char = ['¹','²','³','⁴','⁵','⁶','⁷','⁸','⁹','⁺'];
procedure GetSortIndex(Column: TVirtualTreeColumn; var SortIndex: Integer; var SortDirection: VirtualTrees.TSortDirection);
var i: Integer;
var
SortItem: TSortItem;
begin
SortIndex := -1;
if Column.Owner.Header.Treeview = DataGrid then begin
// Data grid supports multiple sorted columns
for i:=0 to Length(DataGridSortColumns)-1 do begin
if DataGridSortColumns[i].ColumnName = PaintInfo.Column.Text then begin
SortIndex := i;
if DataGridSortColumns[i].SortDirection = ORDER_ASC then
SortItem := FDataGridSortItems.FindByColumn(PaintInfo.Column.Text);
if Assigned(SortItem) then begin
SortIndex := FDataGridSortItems.IndexOf(SortItem);
if SortItem.Order = sioAscending then
SortDirection := sdAscending
else
SortDirection := sdDescending;
Break;
end;
end;
end else begin
// We're in a query grid, supporting a single sorted column
if Column.Owner.Header.SortColumn = Column.Index then begin
@ -5962,10 +5965,10 @@ begin
SynMemoFilter.OnStatusChange(SynMemoFilter, []);
// Append ORDER clause
if Length(DataGridSortColumns) > 0 then begin
Select := Select + ' ORDER BY ' + ComposeOrderClause(DataGridSortColumns);
if FDataGridSortItems.Count > 0 then begin
Select := Select + ' ORDER BY ' + FDataGridSortItems.ComposeOrderClause;
tbtnDataSorting.ImageIndex := 108;
tbtnDataSorting.Caption := _('Sorting') + ' ('+IntToStr(Length(DataGridSortColumns))+')';
tbtnDataSorting.Caption := _('Sorting') + ' ('+IntToStr(FDataGridSortItems.Count)+')';
end else begin
tbtnDataSorting.ImageIndex := 107;
tbtnDataSorting.Caption := _('Sorting');
@ -10287,9 +10290,8 @@ end;
procedure TMainForm.DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo);
var
frm: TForm;
i, j: Integer;
columnexists : Boolean;
ColName: String;
SortItem: TSortItem;
begin
if HitInfo.Column = NoColumn then
Exit;
@ -10300,32 +10302,20 @@ begin
ColName := Sender.Columns[HitInfo.Column].Text;
// Add a new order column after a columns title has been clicked
// Check if order column is already existant
columnexists := False;
for i := Low(DataGridSortColumns) to High(DataGridSortColumns) do begin
if DataGridSortColumns[i].ColumnName = ColName then begin
SortItem := FDataGridSortItems.FindByColumn(ColName);
if Assigned(SortItem) then begin
// AddOrderCol is already in the list. Switch its direction:
// DESC > ASC > [delete col]
columnexists := True;
if DataGridSortColumns[i].SortDirection = ORDER_DESC then
DataGridSortColumns[i].SortDirection := ORDER_ASC
// ASC > DESC > [delete col]
if SortItem.Order = sioAscending then
SortItem.Order := sioDescending
else
FDataGridSortItems.Remove(SortItem);
end
else begin
// Delete order col
for j := i to High(DataGridSortColumns) - 1 do
DataGridSortColumns[j] := DataGridSortColumns[j+1];
SetLength(DataGridSortColumns, Length(DataGridSortColumns)-1);
end;
// We found the matching column, no need to loop further
break;
end;
SortItem := TSortItem.Create(ColName, sioAscending);
FDataGridSortItems.Add(SortItem);
end;
if not columnexists then begin
i := Length(DataGridSortColumns);
SetLength(DataGridSortColumns, i+1);
DataGridSortColumns[i] := TOrderCol.Create;
DataGridSortColumns[i].ColumnName := ColName;
DataGridSortColumns[i].SortDirection := ORDER_DESC;
end;
// Refresh grid, and remember X scroll offset, so the just clicked column is still at the same place.
FDataGridLastClickedColumnHeader := HitInfo.Column;
FDataGridLastClickedColumnLeftPos := Sender.Columns[HitInfo.Column].Left;
@ -10898,8 +10888,9 @@ end;
procedure TMainForm.HandleDataGridAttributes(RefreshingData: Boolean);
var
rx: TRegExpr;
idx, i: Integer;
i: Integer;
Sort, KeyName, FocusedCol, CellFocus, Filter: String;
SortItem: TSortItem;
begin
actDataResetSorting.Enabled := False;
// Clear filter, column names and sort structure if gr
@ -10939,7 +10930,7 @@ begin
if not RefreshingData then begin
DataGridHiddenColumns.Clear;
SynMemoFilter.Clear;
SetLength(DataGridSortColumns, 0);
FDataGridSortItems.Clear;
DataGridWantedRowCount := 0;
while DataGridFocusedNodeIndex >= DataGridWantedRowCount do
Inc(DataGridWantedRowCount, AppSettings.ReadInt(asDatagridRowsPerStep));
@ -10956,8 +10947,10 @@ begin
else if AppSettings.ValueExists(asFilter) then
AppSettings.DeleteValue(asFilter);
for i := 0 to High(DataGridSortColumns) do
Sort := Sort + IntToStr(DataGridSortColumns[i].SortDirection) + '_' + DataGridSortColumns[i].ColumnName + DELIM;
Sort := '';
for SortItem in FDataGridSortItems do begin
Sort := Sort + IntToStr(Integer(SortItem.Order)) + '_' + SortItem.Column + DELIM;
end;
if Sort <> '' then
AppSettings.WriteString(asSort, Sort)
else if AppSettings.ValueExists(asSort) then
@ -10992,26 +10985,25 @@ begin
// Sort
if AppSettings.ValueExists(asSort) then begin
SetLength(DataGridSortColumns, 0);
FDataGridSortItems.Clear;
rx := TRegExpr.Create;
rx.Expression := '\b(\d)_(.+)\'+DELIM;
rx.ModifierG := False;
if rx.Exec(AppSettings.ReadString(asSort)) then while true do begin
idx := Length(DataGridSortColumns);
// Check if column exists, could be renamed or deleted
for i:=0 to SelectedTableColumns.Count-1 do begin
if SelectedTableColumns[i].Name = rx.Match[2] then begin
SetLength(DataGridSortColumns, idx+1);
DataGridSortColumns[idx] := TOrderCol.Create;
DataGridSortColumns[idx].ColumnName := rx.Match[2];
DataGridSortColumns[idx].SortDirection := StrToIntDef(rx.Match[1], ORDER_ASC);
break;
SortItem := TSortItem.Create;
SortItem.Column := rx.Match[2];
SortItem.Order := TSortItemOrder(StrToIntDef(rx.Match[1], 0));
FDataGridSortItems.Add(SortItem);
Break;
end;
end;
if not rx.ExecNext then
break;
end;
actDataResetSorting.Enabled := Length(DataGridSortColumns) > 0;
actDataResetSorting.Enabled := FDataGridSortItems.Count > 0;
end;
AppSettings.ResetPath;
@ -13177,7 +13169,7 @@ end;
procedure TMainForm.actDataResetSortingExecute(Sender: TObject);
begin
SetLength(DataGridSortColumns, 0);
FDataGridSortItems.Clear;
InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False);
end;