From 7ff9b2e9b722dd436e416a4a0147ea5729a87d3d Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Sun, 22 Jan 2023 12:49:53 +0100 Subject: [PATCH] Refactor internal structures for grid/table sorting, prefer TObjectList over Array --- source/apphelpers.pas | 117 ++++++++++++++++++++++++++++++---------- source/const.inc | 6 --- source/data_sorting.pas | 77 +++++++++++--------------- source/dbconnection.pas | 6 ++- source/main.pas | 98 ++++++++++++++++----------------- 5 files changed, 170 insertions(+), 134 deletions(-) diff --git a/source/apphelpers.pas b/source/apphelpers.pas index a142e476..a7933f7d 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -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) + 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 } diff --git a/source/const.inc b/source/const.inc index 1df640a6..5a1296d6 100644 --- a/source/const.inc +++ b/source/const.inc @@ -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; diff --git a/source/data_sorting.pas b/source/data_sorting.pas index f6e340cb..6e3822aa 100644 --- a/source/data_sorting.pas +++ b/source/data_sorting.pas @@ -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 @@ -232,27 +235,14 @@ end; {** Delete order column } -procedure TfrmDataSorting.btnDeleteClick( Sender: TObject ); +procedure TfrmDataSorting.btnDeleteClick(Sender: TObject); var - btn : TButton; - i : Integer; + btn: TButton; 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; + 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; + UnusedColumns.AddStrings(FColumnNames); + 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; diff --git a/source/dbconnection.pas b/source/dbconnection.pas index 3cdc5af0..2a590f21 100644 --- a/source/dbconnection.pas +++ b/source/dbconnection.pas @@ -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'; diff --git a/source/main.pas b/source/main.pas index 266d391a..b32f6df5 100644 --- a/source/main.pas +++ b/source/main.pas @@ -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 - SortDirection := sdAscending - else - SortDirection := sdDescending; - Break; - end; + 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; 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 - // 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 - 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 := FDataGridSortItems.FindByColumn(ColName); + if Assigned(SortItem) then begin + // AddOrderCol is already in the list. Switch its direction: + // ASC > DESC > [delete col] + if SortItem.Order = sioAscending then + SortItem.Order := sioDescending + else + FDataGridSortItems.Remove(SortItem); + end + else begin + 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;