View Issue Details

IDProjectCategoryView StatusLast Update
0034057LazarusLCLpublic2018-11-16 12:06
ReporterAlexey Tor.Assigned ToJuha Manninen 
PrioritynormalSeverityminorReproducibilityhave not tried
Status resolvedResolutionfixed 
Product Version1.9 (SVN)Product Build 
Target VersionFixed in Version 
Summary0034057: LazControls: 3 filter controls need FilterOptions
Description3 filter controls (TListFilterEdit, TTreeFilterEdit, TListviewFilterEdit) don't have props for case-sensitive filter.
Also good: option "filter matches only at beginning of string".
I suggest to add this to base unit (EditBtn.pas)

type
  TFilterStringOption = (
    fsoCaseSensitive,
    fsoMatchOnlyAtStart
    );
  TFilterStringOptions = set of TFilterStringOption;

and add to base TCustomControlFilterEdit prop: FilterOptions.
Actual filtering works in final classes, but filteroptions should be in base class.
Then pls implement filteroptions in 3 filter controls.
Maybe I can make a patch.
TagsNo tags attached.
Fixed in Revisionr58673, r59225, r59226, r59555
LazTarget-
Widgetset
Attached Files
  • tst-fiter-edits.zip (3,436 bytes)
  • f1.diff (7,416 bytes)
    Index: components/lazcontrols/listfilteredit.pas
    ===================================================================
    --- components/lazcontrols/listfilteredit.pas	(revision 58663)
    +++ components/lazcontrols/listfilteredit.pas	(working copy)
    @@ -171,13 +171,12 @@
     // Copy data from fOriginalData to fSortedData in sorted order
     var
       Origi, i: Integer;
    -  Capt, FilterLC: string;
    +  Capt: string;
     begin
       fSortedData.Clear;
    -  FilterLC:=UTF8LowerCase(Filter);
       for Origi:=0 to fOriginalData.Count-1 do begin
         Capt:=fOriginalData[Origi];
    -    if DoFilterItem(Capt, FilterLC, fOriginalData.Objects[Origi]) then begin
    +    if DoFilterItem(Capt, Filter, fOriginalData.Objects[Origi]) then begin
           i:=fSortedData.Count-1;       // Always sort the data.
           while i>=0 do begin
             if CompareFNs(Capt,fSortedData[i])>=0 then break;
    Index: components/lazcontrols/listviewfilteredit.pas
    ===================================================================
    --- components/lazcontrols/listviewfilteredit.pas	(revision 58663)
    +++ components/lazcontrols/listviewfilteredit.pas	(working copy)
    @@ -53,7 +53,7 @@
         fOriginalData: TListViewDataList;
         // Data sorted for viewing.
         fFilteredData: TListViewDataList;
    -    function MatchesFilter(aData: TListViewDataItem; const FilterLC: string): Boolean;
    +    function MatchesFilter(aData: TListViewDataItem; const AFilter: string): Boolean;
         procedure SetFilteredListview(const AValue: TCustomListView);
       protected
         procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    @@ -175,7 +175,7 @@
     end;
     
     function TListViewFilterEdit.MatchesFilter(aData: TListViewDataItem;
    -  const FilterLC: string): Boolean;
    +  const AFilter: string): Boolean;
     var
       i, EndInd: Integer;
     begin
    @@ -184,7 +184,7 @@
       else
         EndInd := 0;
       for i := 0 to EndInd do begin
    -    if DoFilterItem(aData.StringArray[i], FilterLC, aData.Data) then
    +    if DoFilterItem(aData.StringArray[i], AFilter, aData.Data) then
           Exit(True);
       end;
       Result := False;
    @@ -207,13 +207,11 @@
     var
       Origi: Integer;
       Data: TListViewDataItem;
    -  FilterLC: string;
     begin
       fFilteredData.Clear;
    -  FilterLC := UTF8LowerCase(Filter);
       for Origi:=0 to fOriginalData.Count-1 do begin
         Data:=fOriginalData[Origi];
    -    if MatchesFilter(Data, FilterLC) then
    +    if MatchesFilter(Data, Filter) then
           fFilteredData.Add(Data);
       end;
     end;
    Index: components/lazcontrols/treefilteredit.pas
    ===================================================================
    --- components/lazcontrols/treefilteredit.pas	(revision 58663)
    +++ components/lazcontrols/treefilteredit.pas	(working copy)
    @@ -185,6 +185,9 @@
         Result:=0;
     end;
     
    +type
    +  TTreeFilterEditAccess = class(TTreeFilterEdit);
    +
     procedure TTreeFilterBranch.SortAndFilter;
     // Copy data from fOriginalData to fSortedData in sorted order
     var
    @@ -194,7 +197,8 @@
       fSortedData.Clear;
       for Origi:=0 to fOriginalData.Count-1 do begin
         s:=fOriginalData[Origi];
    -    if (fOwner.Filter='') or (Pos(fOwner.Filter,lowercase(s))>0) then begin
    +    if (fOwner.Filter='') or
    +       TTreeFilterEditAccess(fOwner).DoDefaultFilterItem(s,fOwner.Filter,nil) then begin
           i:=fSortedData.Count-1;
           while i>=0 do begin
             if CompareFNs(s,fSortedData[i])>=0 then break;
    @@ -467,11 +471,9 @@
     // Returns True if Node or its siblings or child nodes have visible items.
     var
       Pass, Done: Boolean;
    -  FilterLC: string;
     begin
       Result := False;
       Done := False;
    -  FilterLC:=UTF8LowerCase(Filter);
       while (Node<>nil) and not Done do
       begin
         // Filter with event handler if there is one.
    @@ -478,7 +480,7 @@
         if Assigned(fOnFilterNode) then
           Pass := fOnFilterNode(Node, Done);
         if not (Pass and Done) then
    -      Pass := DoFilterItem(Node.Text, FilterLC, Node.Data);
    +      Pass := DoFilterItem(Node.Text, Filter, Node.Data);
         if Pass and (fFirstPassedNode=Nil) then
           fFirstPassedNode:=Node;
         // Recursive call for child nodes.
    Index: lcl/editbtn.pas
    ===================================================================
    --- lcl/editbtn.pas	(revision 58663)
    +++ lcl/editbtn.pas	(working copy)
    @@ -200,6 +200,9 @@
         property Visible;
       end;
     
    +  TFilterStringOption = (fsoCaseSensitive, fsoMatchOnlyAtStart);
    +  TFilterStringOptions = set of TFilterStringOption;
    +
       // Called when an item is filtered. Returns true if the item passes the filter.
       // Done=False means the data should also be filtered by its title string.
       // Done=True means no other filtering is needed.
    @@ -217,6 +220,7 @@
       TCustomControlFilterEdit = class(TCustomEditButton)
       private
         fFilter: string;
    +    fFilterOptions: TFilterStringOptions;
         fIdleConnected: Boolean;
         fSortData: Boolean;             // Data needs to be sorted.
         fIsFirstSetFormActivate: Boolean;
    @@ -232,9 +236,9 @@
         fOnFilterItem: TFilterItemEvent;
         fOnFilterItemEx: TFilterItemExEvent;
         fOnCheckItem: TCheckItemEvent;
    -    function DoFilterItem(const ACaption, FilterLC: string;
    +    function DoFilterItem(const ACaption, AFilter: string;
           ItemData: Pointer): Boolean;
    -    function DoDefaultFilterItem(const ACaption, FilterLC: string;
    +    function DoDefaultFilterItem(const ACaption, AFilter: string;
           const ItemData: Pointer): Boolean; virtual;
         procedure DestroyWnd; override;
         procedure EditKeyDown(var Key: Word; Shift: TShiftState); override;
    @@ -265,6 +269,8 @@
         property SortData: Boolean read fSortData write fSortData;
         property SelectedPart: TObject read fSelectedPart write fSelectedPart;
       published
    +    property CharCase default ecLowerCase;
    +    property FilterOptions: TFilterStringOptions read fFilterOptions write fFilterOptions default [];
         property OnAfterFilter: TNotifyEvent read fOnAfterFilter write fOnAfterFilter;
         property OnFilterItem: TFilterItemEvent read fOnFilterItem write fOnFilterItem;
           deprecated 'Use OnFilterItemEx with a caption parameter instead.';
    @@ -1112,6 +1118,7 @@
       inherited Create(AOwner);
       CharCase:=ecLowerCase;
       Button.Enabled:=False;
    +  fFilterOptions:=[];
       fIsFirstUpdate:=True;
       fIsFirstSetFormActivate:=True;
       TextHint:=rsFilter;
    @@ -1123,9 +1130,22 @@
     end;
     
     function TCustomControlFilterEdit.DoDefaultFilterItem(const ACaption,
    -  FilterLC: string; const ItemData: Pointer): Boolean;
    +  AFilter: string; const ItemData: Pointer): Boolean;
    +var
    +  NPos: integer;
     begin
    -  Result := (FilterLC='') or (Pos(FilterLC,UTF8LowerCase(ACaption))>0);
    +  if AFilter='' then
    +    exit(True);
    +
    +  if fsoCaseSensitive in fFilterOptions then
    +    NPos := Pos(AFilter, ACaption)
    +  else
    +    NPos := Pos(UTF8LowerCase(AFilter), UTF8LowerCase(ACaption));
    +
    +  if fsoMatchOnlyAtStart in fFilterOptions then
    +    Result := NPos=1
    +  else
    +    Result := NPos>0;
     end;
     
     procedure TCustomControlFilterEdit.DestroyWnd;
    @@ -1135,7 +1155,7 @@
     end;
     
     function TCustomControlFilterEdit.DoFilterItem(const ACaption,
    -  FilterLC: string; ItemData: Pointer): Boolean;
    +  AFilter: string; ItemData: Pointer): Boolean;
     var
       Done: Boolean;
     begin
    @@ -1152,7 +1172,7 @@
     
       // Filter by item's caption text if needed.
       if not (Result or Done) then
    -    Result := DoDefaultFilterItem(ACaption, FilterLC, ItemData);
    +    Result := DoDefaultFilterItem(ACaption, AFilter, ItemData);
     end;
     
     procedure TCustomControlFilterEdit.OnIdle(Sender: TObject; var Done: Boolean);
    
    f1.diff (7,416 bytes)
  • fix1.diff (2,584 bytes)
    Index: components/lazcontrols/treefilteredit.pas
    ===================================================================
    --- components/lazcontrols/treefilteredit.pas	(revision 59173)
    +++ components/lazcontrols/treefilteredit.pas	(working copy)
    @@ -195,7 +195,7 @@
       for Origi:=0 to fOriginalData.Count-1 do begin
         s:=fOriginalData[Origi];
         if (fOwner.Filter='') or
    -        fOwner.DoDefaultFilterItem(s,fOwner.Filter,nil) then begin
    +        fOwner.DoDefaultFilterItem(s,nil) then begin
           i:=fSortedData.Count-1;
           while i>=0 do begin
             if CompareFNs(s,fSortedData[i])>=0 then break;
    Index: lcl/editbtn.pas
    ===================================================================
    --- lcl/editbtn.pas	(revision 59173)
    +++ lcl/editbtn.pas	(working copy)
    @@ -220,6 +220,7 @@
       TCustomControlFilterEdit = class(TCustomEditButton)
       private
         fFilter: string;
    +    fFilterLowercase: string;
         fFilterOptions: TFilterStringOptions;
         fIdleConnected: Boolean;
         fSortData: Boolean;             // Data needs to be sorted.
    @@ -256,7 +257,7 @@
       public
         constructor Create(AOwner: TComponent); override;
         destructor Destroy; override;
    -    function DoDefaultFilterItem(const ACaption, AFilter: string;
    +    function DoDefaultFilterItem(const ACaption: string;
           const ItemData: Pointer): Boolean; virtual;
         procedure InvalidateFilter;
         procedure ResetFilter;
    @@ -1135,18 +1136,18 @@
       inherited DestroyWnd;
     end;
     
    -function TCustomControlFilterEdit.DoDefaultFilterItem(const ACaption,
    -  AFilter: string; const ItemData: Pointer): Boolean;
    +function TCustomControlFilterEdit.DoDefaultFilterItem(const ACaption: string;
    +  const ItemData: Pointer): Boolean;
     var
       NPos: integer;
     begin
    -  if AFilter='' then
    +  if fFilter='' then
         exit(True);
     
       if fsoCaseSensitive in fFilterOptions then
    -    NPos := Pos(AFilter, ACaption)
    +    NPos := Pos(fFilter, ACaption)
       else
    -    NPos := Pos(UTF8LowerCase(AFilter), UTF8LowerCase(ACaption));
    +    NPos := Pos(fFilterLowercase, UTF8LowerCase(ACaption));
     
       if fsoMatchOnlyAtStart in fFilterOptions then
         Result := NPos=1
    @@ -1172,7 +1173,7 @@
     
       // Filter by item's caption text if needed.
       if not (Result or Done) then
    -    Result := DoDefaultFilterItem(ACaption, AFilter, ItemData);
    +    Result := DoDefaultFilterItem(ACaption, ItemData);
     end;
     
     procedure TCustomControlFilterEdit.OnIdle(Sender: TObject; var Done: Boolean);
    @@ -1190,6 +1191,7 @@
       if fFilter=AValue then
         Exit;
       fFilter:=AValue;
    +  fFilterLowercase:=UTF8LowerCase(fFilter);
       ApplyFilter;
     end;
     
    
    fix1.diff (2,584 bytes)
  • editbtn-FilterLowercase-01.patch (591 bytes)
    Index: lcl/editbtn.pas
    ===================================================================
    --- lcl/editbtn.pas	(revision 59384)
    +++ lcl/editbtn.pas	(working copy)
    @@ -266,6 +266,7 @@
         procedure RestoreSelection; virtual; abstract;
       public
         property Filter: string read fFilter write SetFilter;
    +    property FilterLowercase: string read fFilterLowercase;
         property IdleConnected: Boolean read fIdleConnected write SetIdleConnected;
         property SortData: Boolean read fSortData write fSortData;
         property SelectedPart: TObject read fSelectedPart write fSelectedPart;
    
  • editbtn-DoFilterItem-01.patch (3,992 bytes)
    Index: components/lazcontrols/listfilteredit.pas
    ===================================================================
    --- components/lazcontrols/listfilteredit.pas	(revision 59447)
    +++ components/lazcontrols/listfilteredit.pas	(working copy)
    @@ -176,7 +176,7 @@
       fSortedData.Clear;
       for Origi:=0 to fOriginalData.Count-1 do begin
         Capt:=fOriginalData[Origi];
    -    if DoFilterItem(Capt, Filter, fOriginalData.Objects[Origi]) then begin
    +    if DoFilterItem(Capt, fOriginalData.Objects[Origi]) then begin
           i:=fSortedData.Count-1;       // Always sort the data.
           while i>=0 do begin
             if CompareFNs(Capt,fSortedData[i])>=0 then break;
    Index: components/lazcontrols/listviewfilteredit.pas
    ===================================================================
    --- components/lazcontrols/listviewfilteredit.pas	(revision 59447)
    +++ components/lazcontrols/listviewfilteredit.pas	(working copy)
    @@ -53,7 +53,7 @@
         fOriginalData: TListViewDataList;
         // Data sorted for viewing.
         fFilteredData: TListViewDataList;
    -    function MatchesFilter(aData: TListViewDataItem; const AFilter: string): Boolean;
    +    function MatchesFilter(aData: TListViewDataItem): Boolean;
         procedure SetFilteredListview(const AValue: TCustomListView);
       protected
         procedure Notification(AComponent: TComponent; Operation: TOperation); override;
    @@ -174,8 +174,7 @@
       end;
     end;
     
    -function TListViewFilterEdit.MatchesFilter(aData: TListViewDataItem;
    -  const AFilter: string): Boolean;
    +function TListViewFilterEdit.MatchesFilter(aData: TListViewDataItem): Boolean;
     var
       i, EndInd: Integer;
     begin
    @@ -184,7 +183,7 @@
       else
         EndInd := 0;
       for i := 0 to EndInd do begin
    -    if DoFilterItem(aData.StringArray[i], AFilter, aData.Data) then
    +    if DoFilterItem(aData.StringArray[i], aData.Data) then
           Exit(True);
       end;
       Result := False;
    @@ -211,7 +210,7 @@
       fFilteredData.Clear;
       for Origi:=0 to fOriginalData.Count-1 do begin
         Data:=fOriginalData[Origi];
    -    if MatchesFilter(Data, Filter) then
    +    if MatchesFilter(Data) then
           fFilteredData.Add(Data);
       end;
     end;
    Index: components/lazcontrols/treefilteredit.pas
    ===================================================================
    --- components/lazcontrols/treefilteredit.pas	(revision 59447)
    +++ components/lazcontrols/treefilteredit.pas	(working copy)
    @@ -195,7 +195,7 @@
       for Origi:=0 to fOriginalData.Count-1 do begin
         s:=fOriginalData[Origi];
         if (fOwner.Filter='') or
    -        fOwner.DoFilterItem(s, fOwner.Filter, nil) then begin
    +        fOwner.DoFilterItem(s, nil) then begin
           i:=fSortedData.Count-1;
           while i>=0 do begin
             if CompareFNs(s,fSortedData[i]) >= 0 then break;
    @@ -477,7 +477,7 @@
         if Assigned(fOnFilterNode) then
           Pass := fOnFilterNode(Node, Done);
         if not (Pass and Done) then
    -      Pass := DoFilterItem(Node.Text, Filter, Node.Data);
    +      Pass := DoFilterItem(Node.Text, Node.Data);
         if Pass and (fFirstPassedNode=Nil) then
           fFirstPassedNode:=Node;
         // Recursive call for child nodes.
    Index: lcl/editbtn.pas
    ===================================================================
    --- lcl/editbtn.pas	(revision 59447)
    +++ lcl/editbtn.pas	(working copy)
    @@ -240,7 +240,7 @@
         procedure DestroyWnd; override;
         function DoDefaultFilterItem(const ACaption: string;
           const ItemData: Pointer): Boolean; virtual;
    -    function DoFilterItem(const ACaption, AFilter: string;
    +    function DoFilterItem(const ACaption: string;
           ItemData: Pointer): Boolean; virtual;
         procedure EditKeyDown(var Key: Word; Shift: TShiftState); override;
         procedure EditChange; override;
    @@ -1155,8 +1155,8 @@
         Result := NPos>0;
     end;
     
    -function TCustomControlFilterEdit.DoFilterItem(const ACaption,
    -  AFilter: string; ItemData: Pointer): Boolean;
    +function TCustomControlFilterEdit.DoFilterItem(const ACaption: string;
    +  ItemData: Pointer): Boolean;
     var
       Done: Boolean;
     begin
    

Activities

Juha Manninen

2018-07-31 20:36

developer   ~0109792

The filters should not be case-sensitive. It goes against the idea of such filters. A user must be able to quickly type a filtering text and not worry about casing.
MatchOnlyAtStart is a better idea although I don't know any use case where it is needed. Do you have such a use case?

Alexey Tor.

2018-07-31 21:03

reporter   ~0109796

Use cases

- case-sens: for CudaText user wants to filter functions names in C++ source

- at-start: in food app, user wants to find by "Mc" only McDonalds, not other *mc*

Alexey Tor.

2018-07-31 21:08

reporter   ~0109797

Last edited: 2018-07-31 21:09

View 2 revisions

- at-start#2: in music app, user wants to find MC Hammer/ MC Noize/ ..., without lot of other *mc* names.

Juha Manninen

2018-08-02 19:52

developer   ~0109843

Yes, such a patch could be applied. It doesn't change the default behavior.

Alexey Tor.

2018-08-02 21:25

reporter  

tst-fiter-edits.zip (3,436 bytes)

Alexey Tor.

2018-08-02 21:25

reporter  

f1.diff (7,416 bytes)
Index: components/lazcontrols/listfilteredit.pas
===================================================================
--- components/lazcontrols/listfilteredit.pas	(revision 58663)
+++ components/lazcontrols/listfilteredit.pas	(working copy)
@@ -171,13 +171,12 @@
 // Copy data from fOriginalData to fSortedData in sorted order
 var
   Origi, i: Integer;
-  Capt, FilterLC: string;
+  Capt: string;
 begin
   fSortedData.Clear;
-  FilterLC:=UTF8LowerCase(Filter);
   for Origi:=0 to fOriginalData.Count-1 do begin
     Capt:=fOriginalData[Origi];
-    if DoFilterItem(Capt, FilterLC, fOriginalData.Objects[Origi]) then begin
+    if DoFilterItem(Capt, Filter, fOriginalData.Objects[Origi]) then begin
       i:=fSortedData.Count-1;       // Always sort the data.
       while i>=0 do begin
         if CompareFNs(Capt,fSortedData[i])>=0 then break;
Index: components/lazcontrols/listviewfilteredit.pas
===================================================================
--- components/lazcontrols/listviewfilteredit.pas	(revision 58663)
+++ components/lazcontrols/listviewfilteredit.pas	(working copy)
@@ -53,7 +53,7 @@
     fOriginalData: TListViewDataList;
     // Data sorted for viewing.
     fFilteredData: TListViewDataList;
-    function MatchesFilter(aData: TListViewDataItem; const FilterLC: string): Boolean;
+    function MatchesFilter(aData: TListViewDataItem; const AFilter: string): Boolean;
     procedure SetFilteredListview(const AValue: TCustomListView);
   protected
     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
@@ -175,7 +175,7 @@
 end;
 
 function TListViewFilterEdit.MatchesFilter(aData: TListViewDataItem;
-  const FilterLC: string): Boolean;
+  const AFilter: string): Boolean;
 var
   i, EndInd: Integer;
 begin
@@ -184,7 +184,7 @@
   else
     EndInd := 0;
   for i := 0 to EndInd do begin
-    if DoFilterItem(aData.StringArray[i], FilterLC, aData.Data) then
+    if DoFilterItem(aData.StringArray[i], AFilter, aData.Data) then
       Exit(True);
   end;
   Result := False;
@@ -207,13 +207,11 @@
 var
   Origi: Integer;
   Data: TListViewDataItem;
-  FilterLC: string;
 begin
   fFilteredData.Clear;
-  FilterLC := UTF8LowerCase(Filter);
   for Origi:=0 to fOriginalData.Count-1 do begin
     Data:=fOriginalData[Origi];
-    if MatchesFilter(Data, FilterLC) then
+    if MatchesFilter(Data, Filter) then
       fFilteredData.Add(Data);
   end;
 end;
Index: components/lazcontrols/treefilteredit.pas
===================================================================
--- components/lazcontrols/treefilteredit.pas	(revision 58663)
+++ components/lazcontrols/treefilteredit.pas	(working copy)
@@ -185,6 +185,9 @@
     Result:=0;
 end;
 
+type
+  TTreeFilterEditAccess = class(TTreeFilterEdit);
+
 procedure TTreeFilterBranch.SortAndFilter;
 // Copy data from fOriginalData to fSortedData in sorted order
 var
@@ -194,7 +197,8 @@
   fSortedData.Clear;
   for Origi:=0 to fOriginalData.Count-1 do begin
     s:=fOriginalData[Origi];
-    if (fOwner.Filter='') or (Pos(fOwner.Filter,lowercase(s))>0) then begin
+    if (fOwner.Filter='') or
+       TTreeFilterEditAccess(fOwner).DoDefaultFilterItem(s,fOwner.Filter,nil) then begin
       i:=fSortedData.Count-1;
       while i>=0 do begin
         if CompareFNs(s,fSortedData[i])>=0 then break;
@@ -467,11 +471,9 @@
 // Returns True if Node or its siblings or child nodes have visible items.
 var
   Pass, Done: Boolean;
-  FilterLC: string;
 begin
   Result := False;
   Done := False;
-  FilterLC:=UTF8LowerCase(Filter);
   while (Node<>nil) and not Done do
   begin
     // Filter with event handler if there is one.
@@ -478,7 +480,7 @@
     if Assigned(fOnFilterNode) then
       Pass := fOnFilterNode(Node, Done);
     if not (Pass and Done) then
-      Pass := DoFilterItem(Node.Text, FilterLC, Node.Data);
+      Pass := DoFilterItem(Node.Text, Filter, Node.Data);
     if Pass and (fFirstPassedNode=Nil) then
       fFirstPassedNode:=Node;
     // Recursive call for child nodes.
Index: lcl/editbtn.pas
===================================================================
--- lcl/editbtn.pas	(revision 58663)
+++ lcl/editbtn.pas	(working copy)
@@ -200,6 +200,9 @@
     property Visible;
   end;
 
+  TFilterStringOption = (fsoCaseSensitive, fsoMatchOnlyAtStart);
+  TFilterStringOptions = set of TFilterStringOption;
+
   // Called when an item is filtered. Returns true if the item passes the filter.
   // Done=False means the data should also be filtered by its title string.
   // Done=True means no other filtering is needed.
@@ -217,6 +220,7 @@
   TCustomControlFilterEdit = class(TCustomEditButton)
   private
     fFilter: string;
+    fFilterOptions: TFilterStringOptions;
     fIdleConnected: Boolean;
     fSortData: Boolean;             // Data needs to be sorted.
     fIsFirstSetFormActivate: Boolean;
@@ -232,9 +236,9 @@
     fOnFilterItem: TFilterItemEvent;
     fOnFilterItemEx: TFilterItemExEvent;
     fOnCheckItem: TCheckItemEvent;
-    function DoFilterItem(const ACaption, FilterLC: string;
+    function DoFilterItem(const ACaption, AFilter: string;
       ItemData: Pointer): Boolean;
-    function DoDefaultFilterItem(const ACaption, FilterLC: string;
+    function DoDefaultFilterItem(const ACaption, AFilter: string;
       const ItemData: Pointer): Boolean; virtual;
     procedure DestroyWnd; override;
     procedure EditKeyDown(var Key: Word; Shift: TShiftState); override;
@@ -265,6 +269,8 @@
     property SortData: Boolean read fSortData write fSortData;
     property SelectedPart: TObject read fSelectedPart write fSelectedPart;
   published
+    property CharCase default ecLowerCase;
+    property FilterOptions: TFilterStringOptions read fFilterOptions write fFilterOptions default [];
     property OnAfterFilter: TNotifyEvent read fOnAfterFilter write fOnAfterFilter;
     property OnFilterItem: TFilterItemEvent read fOnFilterItem write fOnFilterItem;
       deprecated 'Use OnFilterItemEx with a caption parameter instead.';
@@ -1112,6 +1118,7 @@
   inherited Create(AOwner);
   CharCase:=ecLowerCase;
   Button.Enabled:=False;
+  fFilterOptions:=[];
   fIsFirstUpdate:=True;
   fIsFirstSetFormActivate:=True;
   TextHint:=rsFilter;
@@ -1123,9 +1130,22 @@
 end;
 
 function TCustomControlFilterEdit.DoDefaultFilterItem(const ACaption,
-  FilterLC: string; const ItemData: Pointer): Boolean;
+  AFilter: string; const ItemData: Pointer): Boolean;
+var
+  NPos: integer;
 begin
-  Result := (FilterLC='') or (Pos(FilterLC,UTF8LowerCase(ACaption))>0);
+  if AFilter='' then
+    exit(True);
+
+  if fsoCaseSensitive in fFilterOptions then
+    NPos := Pos(AFilter, ACaption)
+  else
+    NPos := Pos(UTF8LowerCase(AFilter), UTF8LowerCase(ACaption));
+
+  if fsoMatchOnlyAtStart in fFilterOptions then
+    Result := NPos=1
+  else
+    Result := NPos>0;
 end;
 
 procedure TCustomControlFilterEdit.DestroyWnd;
@@ -1135,7 +1155,7 @@
 end;
 
 function TCustomControlFilterEdit.DoFilterItem(const ACaption,
-  FilterLC: string; ItemData: Pointer): Boolean;
+  AFilter: string; ItemData: Pointer): Boolean;
 var
   Done: Boolean;
 begin
@@ -1152,7 +1172,7 @@
 
   // Filter by item's caption text if needed.
   if not (Result or Done) then
-    Result := DoDefaultFilterItem(ACaption, FilterLC, ItemData);
+    Result := DoDefaultFilterItem(ACaption, AFilter, ItemData);
 end;
 
 procedure TCustomControlFilterEdit.OnIdle(Sender: TObject; var Done: Boolean);
f1.diff (7,416 bytes)

Alexey Tor.

2018-08-02 21:28

reporter   ~0109845

Last edited: 2018-08-02 21:29

View 2 revisions

Patch added, and demo to test all 3 controls.

Filtering is now little slower, coz before LCL has var FilterLC (lowercase), now it's deleted.
but: only a little slower. because default CharCase property is "lower case",
so UTF8LowerCase() call will be for lowercase string, and it is fast.

published CharCase prop + FilterOptions prop in all 3 controls.

Juha Manninen

2018-08-04 23:31

developer   ~0109885

I applied the patch after small changes.
I removed the class accessor hack and changed DoDefaultFilterItem visibility.
We have full control over these components and they don't need to be compatible with any other system.

Ondrej Pokorny

2018-09-23 19:24

reporter   ~0110984

Why is DoDefaultFilterItem made public?

Ondrej Pokorny

2018-09-23 19:44

reporter   ~0110985

You shouldn't call DoDefaultFilterItem in TTreeFilterBranch.SortAndFilter but DoFilterItem. You already had to fix the code in DoFilterItem call in TTreeFilterEdit.FilterTree - so you know about it. Why do you call DoDefaultFilterItem in TTreeFilterBranch.SortAndFilter instead of DoFilterItem?

IMO DoFilterItem should be made virtual as well. You should add a public method FilterItem that simply calls DoFilterItem and call FilterItem in TTreeFilterBranch.SortAndFilter. (This is the common approach - the Do* methods are usually protected - and shouldn't be exposed to public.)

+++

Furthermore, now the UTF8LowerCase(AFilter) is done in DoDefaultFilterItem that has these drawbacks and regressions:
1.) performance: it has to be called for every item all over again - before it was called only once (which makes sense).
2.) the events fOnFilterItemEx&fOnFilterItem are now called with original filter case and not lowercased like before (if fsoCaseSensitive is disabled).

Alexey Tor.

2018-09-24 08:54

reporter   ~0110990

agree.
a) you can add methods.
b) i don't know what to do with lowercase param.

Ondrej Pokorny

2018-09-24 12:22

reporter   ~0110992

I'll make a patch.

Juha Manninen

2018-09-26 10:42

developer   ~0111023

Reopening.
I was not happy with this AlexeyT's change either but I still applied it.
Now I was planning to revert the whole thing but if Ondrej comes up with a better patch then I apply it.

Alexey Tor.

2018-09-27 09:14

reporter   ~0111047

revert all?
O_o

pls make optimization, not revert. e.g., LowerCase can be removed and some sting case-insens compare used. (StrIComp?)

Alexey Tor.

2018-09-27 10:35

reporter  

fix1.diff (2,584 bytes)
Index: components/lazcontrols/treefilteredit.pas
===================================================================
--- components/lazcontrols/treefilteredit.pas	(revision 59173)
+++ components/lazcontrols/treefilteredit.pas	(working copy)
@@ -195,7 +195,7 @@
   for Origi:=0 to fOriginalData.Count-1 do begin
     s:=fOriginalData[Origi];
     if (fOwner.Filter='') or
-        fOwner.DoDefaultFilterItem(s,fOwner.Filter,nil) then begin
+        fOwner.DoDefaultFilterItem(s,nil) then begin
       i:=fSortedData.Count-1;
       while i>=0 do begin
         if CompareFNs(s,fSortedData[i])>=0 then break;
Index: lcl/editbtn.pas
===================================================================
--- lcl/editbtn.pas	(revision 59173)
+++ lcl/editbtn.pas	(working copy)
@@ -220,6 +220,7 @@
   TCustomControlFilterEdit = class(TCustomEditButton)
   private
     fFilter: string;
+    fFilterLowercase: string;
     fFilterOptions: TFilterStringOptions;
     fIdleConnected: Boolean;
     fSortData: Boolean;             // Data needs to be sorted.
@@ -256,7 +257,7 @@
   public
     constructor Create(AOwner: TComponent); override;
     destructor Destroy; override;
-    function DoDefaultFilterItem(const ACaption, AFilter: string;
+    function DoDefaultFilterItem(const ACaption: string;
       const ItemData: Pointer): Boolean; virtual;
     procedure InvalidateFilter;
     procedure ResetFilter;
@@ -1135,18 +1136,18 @@
   inherited DestroyWnd;
 end;
 
-function TCustomControlFilterEdit.DoDefaultFilterItem(const ACaption,
-  AFilter: string; const ItemData: Pointer): Boolean;
+function TCustomControlFilterEdit.DoDefaultFilterItem(const ACaption: string;
+  const ItemData: Pointer): Boolean;
 var
   NPos: integer;
 begin
-  if AFilter='' then
+  if fFilter='' then
     exit(True);
 
   if fsoCaseSensitive in fFilterOptions then
-    NPos := Pos(AFilter, ACaption)
+    NPos := Pos(fFilter, ACaption)
   else
-    NPos := Pos(UTF8LowerCase(AFilter), UTF8LowerCase(ACaption));
+    NPos := Pos(fFilterLowercase, UTF8LowerCase(ACaption));
 
   if fsoMatchOnlyAtStart in fFilterOptions then
     Result := NPos=1
@@ -1172,7 +1173,7 @@
 
   // Filter by item's caption text if needed.
   if not (Result or Done) then
-    Result := DoDefaultFilterItem(ACaption, AFilter, ItemData);
+    Result := DoDefaultFilterItem(ACaption, ItemData);
 end;
 
 procedure TCustomControlFilterEdit.OnIdle(Sender: TObject; var Done: Boolean);
@@ -1190,6 +1191,7 @@
   if fFilter=AValue then
     Exit;
   fFilter:=AValue;
+  fFilterLowercase:=UTF8LowerCase(fFilter);
   ApplyFilter;
 end;
 
fix1.diff (2,584 bytes)

Alexey Tor.

2018-09-27 10:35

reporter   ~0111050

added fix1.diff, Ondrej, is it ok?

Juha Manninen

2018-10-02 13:42

developer   ~0111195

I applied the patch with modifications.
Protected DoDefaultFilterItem and DoFilterItem can be called from TTreeFilterBranch directly. Public visibility was wrong.

Ondrej Pokorny

2018-11-04 21:21

reporter   ~0111790

(Problem A.) There is still the problem with "2.) the events fOnFilterItemEx&fOnFilterItem are now called with original filter case and not lowercased like before (if fsoCaseSensitive is disabled)." that I pointed out above.

Applications that expect the filter to be lowercase (like it was before your modifications) do not have access to the private fFilterLowercase field. You should make a public read-only property. See attached patch editbtn-FilterLowercase-01.patch.

(Problem B.) This is more a code-design issue. You removed the FilterLC parameter from DoDefaultFilterItem. Yet you kept it in DoFilterItem and it is not used there anymore. You should either delete both filter parameters or keep them (or use both Filter and FilterLC parameters). The solution is up to you.
See editbtn-DoFilterItem-01.patch - I deleted the unused parameter here.

Ondrej Pokorny

2018-11-04 21:21

reporter  

editbtn-FilterLowercase-01.patch (591 bytes)
Index: lcl/editbtn.pas
===================================================================
--- lcl/editbtn.pas	(revision 59384)
+++ lcl/editbtn.pas	(working copy)
@@ -266,6 +266,7 @@
     procedure RestoreSelection; virtual; abstract;
   public
     property Filter: string read fFilter write SetFilter;
+    property FilterLowercase: string read fFilterLowercase;
     property IdleConnected: Boolean read fIdleConnected write SetIdleConnected;
     property SortData: Boolean read fSortData write fSortData;
     property SelectedPart: TObject read fSelectedPart write fSelectedPart;

Ondrej Pokorny

2018-11-04 21:21

reporter  

editbtn-DoFilterItem-01.patch (3,992 bytes)
Index: components/lazcontrols/listfilteredit.pas
===================================================================
--- components/lazcontrols/listfilteredit.pas	(revision 59447)
+++ components/lazcontrols/listfilteredit.pas	(working copy)
@@ -176,7 +176,7 @@
   fSortedData.Clear;
   for Origi:=0 to fOriginalData.Count-1 do begin
     Capt:=fOriginalData[Origi];
-    if DoFilterItem(Capt, Filter, fOriginalData.Objects[Origi]) then begin
+    if DoFilterItem(Capt, fOriginalData.Objects[Origi]) then begin
       i:=fSortedData.Count-1;       // Always sort the data.
       while i>=0 do begin
         if CompareFNs(Capt,fSortedData[i])>=0 then break;
Index: components/lazcontrols/listviewfilteredit.pas
===================================================================
--- components/lazcontrols/listviewfilteredit.pas	(revision 59447)
+++ components/lazcontrols/listviewfilteredit.pas	(working copy)
@@ -53,7 +53,7 @@
     fOriginalData: TListViewDataList;
     // Data sorted for viewing.
     fFilteredData: TListViewDataList;
-    function MatchesFilter(aData: TListViewDataItem; const AFilter: string): Boolean;
+    function MatchesFilter(aData: TListViewDataItem): Boolean;
     procedure SetFilteredListview(const AValue: TCustomListView);
   protected
     procedure Notification(AComponent: TComponent; Operation: TOperation); override;
@@ -174,8 +174,7 @@
   end;
 end;
 
-function TListViewFilterEdit.MatchesFilter(aData: TListViewDataItem;
-  const AFilter: string): Boolean;
+function TListViewFilterEdit.MatchesFilter(aData: TListViewDataItem): Boolean;
 var
   i, EndInd: Integer;
 begin
@@ -184,7 +183,7 @@
   else
     EndInd := 0;
   for i := 0 to EndInd do begin
-    if DoFilterItem(aData.StringArray[i], AFilter, aData.Data) then
+    if DoFilterItem(aData.StringArray[i], aData.Data) then
       Exit(True);
   end;
   Result := False;
@@ -211,7 +210,7 @@
   fFilteredData.Clear;
   for Origi:=0 to fOriginalData.Count-1 do begin
     Data:=fOriginalData[Origi];
-    if MatchesFilter(Data, Filter) then
+    if MatchesFilter(Data) then
       fFilteredData.Add(Data);
   end;
 end;
Index: components/lazcontrols/treefilteredit.pas
===================================================================
--- components/lazcontrols/treefilteredit.pas	(revision 59447)
+++ components/lazcontrols/treefilteredit.pas	(working copy)
@@ -195,7 +195,7 @@
   for Origi:=0 to fOriginalData.Count-1 do begin
     s:=fOriginalData[Origi];
     if (fOwner.Filter='') or
-        fOwner.DoFilterItem(s, fOwner.Filter, nil) then begin
+        fOwner.DoFilterItem(s, nil) then begin
       i:=fSortedData.Count-1;
       while i>=0 do begin
         if CompareFNs(s,fSortedData[i]) >= 0 then break;
@@ -477,7 +477,7 @@
     if Assigned(fOnFilterNode) then
       Pass := fOnFilterNode(Node, Done);
     if not (Pass and Done) then
-      Pass := DoFilterItem(Node.Text, Filter, Node.Data);
+      Pass := DoFilterItem(Node.Text, Node.Data);
     if Pass and (fFirstPassedNode=Nil) then
       fFirstPassedNode:=Node;
     // Recursive call for child nodes.
Index: lcl/editbtn.pas
===================================================================
--- lcl/editbtn.pas	(revision 59447)
+++ lcl/editbtn.pas	(working copy)
@@ -240,7 +240,7 @@
     procedure DestroyWnd; override;
     function DoDefaultFilterItem(const ACaption: string;
       const ItemData: Pointer): Boolean; virtual;
-    function DoFilterItem(const ACaption, AFilter: string;
+    function DoFilterItem(const ACaption: string;
       ItemData: Pointer): Boolean; virtual;
     procedure EditKeyDown(var Key: Word; Shift: TShiftState); override;
     procedure EditChange; override;
@@ -1155,8 +1155,8 @@
     Result := NPos>0;
 end;
 
-function TCustomControlFilterEdit.DoFilterItem(const ACaption,
-  AFilter: string; ItemData: Pointer): Boolean;
+function TCustomControlFilterEdit.DoFilterItem(const ACaption: string;
+  ItemData: Pointer): Boolean;
 var
   Done: Boolean;
 begin

Alexey Tor.

2018-11-05 17:19

reporter   ~0111800

Seems both patches are valid.

Juha Manninen

2018-11-16 12:06

developer   ~0111994

I applied the patches. Thanks Ondrej.

Issue History

Date Modified Username Field Change
2018-07-30 14:16 Alexey Tor. New Issue
2018-07-31 20:36 Juha Manninen Note Added: 0109792
2018-07-31 21:03 Alexey Tor. Note Added: 0109796
2018-07-31 21:08 Alexey Tor. Note Added: 0109797
2018-07-31 21:09 Alexey Tor. Note Edited: 0109797 View Revisions
2018-08-02 19:52 Juha Manninen Note Added: 0109843
2018-08-02 21:25 Alexey Tor. File Added: tst-fiter-edits.zip
2018-08-02 21:25 Alexey Tor. File Added: f1.diff
2018-08-02 21:28 Alexey Tor. Note Added: 0109845
2018-08-02 21:29 Alexey Tor. Note Edited: 0109845 View Revisions
2018-08-04 23:31 Juha Manninen Fixed in Revision => r58673
2018-08-04 23:31 Juha Manninen LazTarget => -
2018-08-04 23:31 Juha Manninen Note Added: 0109885
2018-08-04 23:31 Juha Manninen Status new => resolved
2018-08-04 23:31 Juha Manninen Resolution open => fixed
2018-08-04 23:31 Juha Manninen Assigned To => Juha Manninen
2018-09-23 19:24 Ondrej Pokorny Note Added: 0110984
2018-09-23 19:44 Ondrej Pokorny Note Added: 0110985
2018-09-24 08:54 Alexey Tor. Note Added: 0110990
2018-09-24 12:22 Ondrej Pokorny Note Added: 0110992
2018-09-26 10:42 Juha Manninen Note Added: 0111023
2018-09-26 10:42 Juha Manninen Status resolved => assigned
2018-09-26 10:42 Juha Manninen Resolution fixed => reopened
2018-09-27 09:14 Alexey Tor. Note Added: 0111047
2018-09-27 10:35 Alexey Tor. File Added: fix1.diff
2018-09-27 10:35 Alexey Tor. Note Added: 0111050
2018-10-02 13:42 Juha Manninen Fixed in Revision r58673 => r58673, r59225, r59226
2018-10-02 13:42 Juha Manninen Note Added: 0111195
2018-10-02 13:42 Juha Manninen Status assigned => resolved
2018-10-02 13:42 Juha Manninen Resolution reopened => fixed
2018-11-04 21:21 Ondrej Pokorny Note Added: 0111790
2018-11-04 21:21 Ondrej Pokorny File Added: editbtn-FilterLowercase-01.patch
2018-11-04 21:21 Ondrej Pokorny File Added: editbtn-DoFilterItem-01.patch
2018-11-05 17:19 Alexey Tor. Note Added: 0111800
2018-11-14 21:02 Juha Manninen Status resolved => assigned
2018-11-14 21:02 Juha Manninen Resolution fixed => reopened
2018-11-16 12:06 Juha Manninen Fixed in Revision r58673, r59225, r59226 => r58673, r59225, r59226, r59555
2018-11-16 12:06 Juha Manninen Note Added: 0111994
2018-11-16 12:06 Juha Manninen Status assigned => resolved
2018-11-16 12:06 Juha Manninen Resolution reopened => fixed