1{%MainUnit ../comctrls.pp}
2{
3 *****************************************************************************
4  This file is part of the Lazarus Component Library (LCL)
5
6  See the file COPYING.modifiedLGPL.txt, included in this distribution,
7  for details about the license.
8 *****************************************************************************
9}
10
11{ TIconOptions }
12
13procedure TIconOptions.SetArrangement(Value: TIconArrangement);
14begin
15  if FArrangement <> Value then
16  begin
17    FArrangement := Value;
18    if FListView.HandleAllocated then
19      TWSCustomListViewClass(FListView.WidgetSetClass).SetIconArrangement(FListView, Arrangement);
20  end;
21end;
22
23function TIconOptions.GetAutoArrange: Boolean;
24begin
25  Result := FListView.GetProperty(Ord(lvpAutoArrange));
26end;
27
28function TIconOptions.GetWrapText: Boolean;
29begin
30  Result := FListView.GetProperty(Ord(lvpWrapText));
31end;
32
33procedure TIconOptions.SetAutoArrange(Value: Boolean);
34begin
35  FListView.SetProperty(Ord(lvpAutoArrange), Value);
36end;
37
38procedure TIconOptions.SetWrapText(Value: Boolean);
39begin
40  FListView.SetProperty(Ord(lvpWrapText), Value);
41end;
42
43procedure TIconOptions.AssignTo(Dest: TPersistent);
44var
45  DestOptions: TIconOptions absolute Dest;
46begin
47  if Dest is TIconOptions then
48  begin
49    DestOptions.Arrangement := Arrangement;
50    DestOptions.AutoArrange := AutoArrange;
51    DestOptions.WrapText := WrapText;
52  end
53  else
54    inherited AssignTo(Dest);
55end;
56
57function TIconOptions.GetOwner: TPersistent;
58begin
59  Result := FListView;
60end;
61
62constructor TIconOptions.Create(AOwner: TCustomListView);
63begin
64  inherited Create;
65  FListView := AOwner;
66  FArrangement := iaTop;
67end;
68
69{ TCustomListViewEditor }
70
71procedure TCustomListViewEditor.ListViewEditorKeyDown(Sender: TObject;
72  var Key: Word; Shift: TShiftState);
73begin
74  if (Shift = []) and Visible then
75  begin
76    if Key = VK_ESCAPE then
77    begin
78      Key := 0;
79      FItem := nil;
80      Visible := False;
81      Parent.SetFocus;
82    end else
83    if Key = VK_RETURN then
84    begin
85      Key := 0;
86      Parent.SetFocus;
87    end;
88  end;
89end;
90
91procedure TCustomListViewEditor.DoExit;
92begin
93  TCustomListView(Parent).HideEditor;
94  inherited DoExit;
95end;
96
97constructor TCustomListViewEditor.Create(AOwner: TComponent);
98begin
99  inherited Create(AOwner);
100  FItem := nil;
101  OnKeyDown := @ListViewEditorKeyDown;
102end;
103
104{------------------------------------------------------------------------------
105   TCustomListView Constructor
106------------------------------------------------------------------------------}
107constructor TCustomListView.Create(AOwner: TComponent);
108var
109  lvil: TListViewImageList;
110begin
111  inherited Create(AOwner);
112  ControlStyle := ControlStyle - [csCaptureMouse];
113  FAutoSort := True;
114  FAutoWidthLastColumn := False;
115  FSortDirection := sdAscending;
116  FIconOptions := TIconOptions.Create(Self);
117  FColumns := TListColumns.Create(Self);
118  FListItems := CreateListItems;
119  BorderStyle := bsSingle;
120  FScrollBars := ssBoth;
121  FCompStyle := csListView;
122  FViewStyle := vsList;
123  FSortType := stNone;
124  FSortColumn := -1;
125
126  for lvil := Low(TListViewImageList) to High(TListViewImageList) do
127  begin
128    FImageChangeLinks[lvil] := TChangeLink.Create;
129    FImageChangeLinks[lvil].OnChange := @ImageChanged;
130    FImageChangeLinks[lvil].OnDestroyResolutionHandle := @ImageResolutionHandleDestroyed;
131  end;
132  FHoverTime := -1;
133  TabStop := true;
134  with GetControlClassDefaultSize do
135    SetInitialBounds(0, 0, CX, CY);
136  ParentColor := False;
137  Color := {$ifdef UseCLDefault}clDefault{$else}clWindow{$endif};
138  FCanvas := TControlCanvas.Create;
139  TControlCanvas(FCanvas).Control := Self;
140  FProperties := [lvpColumnClick, lvpHideSelection, lvpShowColumnHeaders, lvpToolTips, lvpWrapText];
141
142  FOwnerDataItem := TOwnerDataListItem.Create(FListItems);
143  FEditor := TCustomListViewEditor.Create(Self);
144  FEditor.ControlStyle := FEditor.ControlStyle + [csNoDesignVisible, csNoDesignSelectable];
145  FEditor.AutoSize := False;
146  FEditor.Visible := False;
147  FEditor.Parent := Self;
148end;
149
150{------------------------------------------------------------------------------
151   TCustomListView CustomDraw
152------------------------------------------------------------------------------}
153function TCustomListView.CustomDraw(const ARect: TRect; AStage: TCustomDrawStage): Boolean;
154begin
155  Result := True;
156  if Assigned(FOnCustomDraw) and (AStage = cdPrePaint)
157  then FOnCustomDraw(Self, ARect, Result);
158
159  if Assigned(FOnAdvancedCustomDraw)
160  then FOnAdvancedCustomDraw(Self, ARect, AStage, Result)
161end;
162
163{------------------------------------------------------------------------------}
164{   TCustomListView CustomDrawItem                                             }
165{------------------------------------------------------------------------------}
166function TCustomListView.CustomDrawItem(AItem: TListItem; AState: TCustomDrawState; AStage: TCustomDrawStage): Boolean;
167begin
168  Result := True;
169  if Assigned(FOnCustomDrawItem) and (AStage = cdPrePaint)
170  then FOnCustomDrawItem(Self, AItem, AState, Result);
171
172  if Assigned(FOnAdvancedCustomDrawItem)
173  then FOnAdvancedCustomDrawItem(Self, AItem, AState, AStage, Result);
174end;
175
176{------------------------------------------------------------------------------}
177{   TCustomListView CustomDrawSubItem                                          }
178{------------------------------------------------------------------------------}
179function TCustomListView.CustomDrawSubItem(AItem: TListItem; ASubItem: Integer; AState: TCustomDrawState; AStage: TCustomDrawStage): Boolean;
180begin
181  Result := True;
182  if Assigned(FOnCustomDrawSubItem) and (AStage = cdPrePaint)
183  then FOnCustomDrawSubItem(Self, AItem, ASubItem, AState, Result);
184
185  if Assigned(FOnAdvancedCustomDrawSubItem)
186  then FOnAdvancedCustomDrawSubItem(Self, AItem, ASubItem, AState, AStage, Result);
187
188end;
189
190{------------------------------------------------------------------------------}
191{   TCustomListView Change                                                     }
192{------------------------------------------------------------------------------}
193procedure TCustomListView.Change(AItem: TListItem; AChange: Integer);
194var
195  ItemChange: TItemChange;
196begin
197  case AChange of
198    LVIF_TEXT: ItemChange := ctText;
199    LVIF_IMAGE: ItemChange := ctImage;
200    LVIF_STATE: ItemChange := ctState;
201  else
202    Exit;
203  end;
204  if Assigned(FOnChange)
205  then FOnChange(Self, AItem, ItemChange);
206end;
207
208{------------------------------------------------------------------------------}
209{   TCustomListView ColClick                                                   }
210{------------------------------------------------------------------------------}
211procedure TCustomListView.ColClick(AColumn: TListColumn);
212const
213  DirToIndicator : array [TSortDirection] of TSortIndicator = (siAscending, siDescending);
214var
215  i: Integer;
216begin
217  if IsEditing then
218  begin
219    if FEditor.Focused then
220    begin
221      SetFocus;
222      HideEditor;
223    end;
224  end;
225  if Assigned(FOnColumnClick) and ColumnClick then FOnColumnClick(Self, AColumn);
226  // we set autosort after FOnColumnClick, maybe programmer want to
227  // stop autosorting after some special column is clicked.
228  if FAutoSort then
229  begin
230    if SortType <> stNone then
231    begin
232      if AColumn.Index <> SortColumn then begin
233        if FAutoSortIndicator then
234          for i:=0 to Columns.Count-1 do
235            if (i <> AColumn.Index) and (Columns[i].SortIndicator <> siNone) then
236              Columns[i].SortIndicator := siNone;
237
238        SortColumn := AColumn.Index;
239        SortDirection := sdAscending;
240        if FAutoSortIndicator then AColumn.SortIndicator := siAscending;
241      end
242      else
243      begin
244        // with same column we are changing only direction
245        if SortDirection = sdAscending then
246          SortDirection := sdDescending
247        else
248          SortDirection := sdAscending;
249        if FAutoSortIndicator then AColumn.SortIndicator := DirToIndicator[SortDirection];
250      end;
251    end;
252  end;
253end;
254
255{------------------------------------------------------------------------------}
256{   TCustomListView CNNotify                                                   }
257{------------------------------------------------------------------------------}
258procedure TCustomListView.CNNotify(var AMessage: TLMNotify);
259var
260  nm: PNMListView;
261  Item: TListItem;
262  n: Integer;
263begin
264  nm := PNMListView(AMessage.NMHdr);
265  // ignore any notifications while initializing items
266  if (nm^.iItem >= Items.Count) or
267     not (OwnerData or (lisfWSItemsCreated in FListItems.Flags)) then Exit;
268  //remark: NMHdr^.code is normally unhanged by the win32 interface, so the others
269  //        maps there codes to the of win32
270  case AMessage.NMHdr^.code of
271//  HDN_TRACK:
272//  NM_CUSTOMDRAW:
273      // Custom Drawing is handled direct from the interfaces by IntfCustomDraw
274//  LVN_BEGINDRAG:
275    LVN_DELETEITEM: begin
276      Item := FListItems[nm^.iItem];
277      if FSelected = Item then
278        InvalidateSelected;
279      if Item = nil then Exit; //? nm^.iItem > Items.Count ?
280      Exclude(Item.FFlags, lifCreated);
281      if not (lifDestroying in Item.FFlags)
282      then Item.Delete;
283    end;
284    LVN_DELETEALLITEMS: begin
285      InvalidateSelected;
286      for n := FListItems.Count - 1 downto 0 do
287      begin
288        Item := FListItems[n];
289        Exclude(Item.FFlags, lifCreated);
290        if not (lifDestroying in Item.FFlags)
291        then Item.Delete;
292      end;
293    end;
294//    LVN_GETDISPINFO:
295//    LVN_ODCACHEHINT:
296//    LVN_ODFINDITEM:
297//    LVN_ODSTATECHANGED:
298//    LVN_BEGINLABELEDIT: implemented via TCustomListViewEditor
299//    LVN_ENDLABELEDIT:   implemented via TCustomListViewEditor
300    LVN_COLUMNCLICK:
301    begin
302      ColClick(Columns[nm^.iSubItem]);
303    end;
304    LVN_INSERTITEM: begin
305      // don't call insert yet,
306      // there is no solution available when we have inserted the item first
307      // see delete
308      // besides... who's inserting items
309    end;
310    LVN_ITEMCHANGING: begin
311      //Check
312    end;
313    LVN_ITEMCHANGED:
314    begin
315      if nm^.iItem < 0 then
316        Item := nil
317      else
318        Item := Items[nm^.iItem];
319      //DebugLn('TCustomListView.CNNotify Count=',dbgs(Items.Count),' nm^.iItem=',dbgs(nm^.iItem),' destroying=',dbgs(lifDestroying in Item.FFlags));
320      if (Item <> nil) and (not OwnerData) and (lifDestroying in Item.FFlags) then
321      begin
322        if Item=FFocused then
323          FFocused:=nil;
324        if Item=FSelected then
325          InvalidateSelected;
326      end else
327      begin
328        if (nm^.uChanged = LVIF_STATE) then
329        begin
330          // checkbox
331          if Checkboxes then
332            DoItemChecked(Item);
333
334          // focus
335          if (nm^.uOldState and LVIS_FOCUSED) <> (nm^.uNewState and LVIS_FOCUSED) then
336          begin
337            // focus state changed
338            if (nm^.uNewState and LVIS_FOCUSED) = 0 then
339            begin
340              if FFocused = Item then
341                FFocused := nil;
342            end
343            else
344              FFocused := Item;
345          end;
346          // select
347          if (((nm^.uOldState and LVIS_SELECTED) <> (nm^.uNewState and LVIS_SELECTED)))
348          or (not (lffSelectedValid in FFlags) and (nm^.uNewState and LVIS_SELECTED <> 0)) then
349          begin
350            // select state changed
351            if (nm^.uNewState and LVIS_SELECTED) = 0 then
352            begin
353              if not OwnerData then
354              begin
355                if FSelected = Item then
356                  InvalidateSelected
357              end else
358                if (nm^.iItem < 0) or (nm^.iItem = FSelectedIdx) then
359                  InvalidateSelected;
360            end else
361            begin
362              FSelected := Item;
363              Include(FFlags,lffSelectedValid);
364              if OwnerData then
365                FSelectedIdx:=nm^.iItem;
366              //DebugLn('TCustomListView.CNNotify FSelected=',dbgs(FSelected));
367            end;
368            Change(Item, nm^.uChanged);
369            DoSelectItem(Item, (nm^.uNewState and LVIS_SELECTED) <> 0);
370          end;
371        end
372        else
373          Change(Item, nm^.uChanged);
374      end;
375    end;
376//    LVN_GETINFOTIP:
377//    NM_CLICK:
378//    NM_RCLICK:
379  end;
380end;
381
382procedure TCustomListView.DrawItem(AItem: TListItem; ARect: TRect;
383  AState: TOwnerDrawState);
384begin
385  if Assigned(FOnDrawItem) then FOnDrawItem(Self, AItem, ARect, AState)
386  else
387  begin
388    FCanvas.FillRect(ARect);
389    FCanvas.TextOut(ARect.Left + 2, ARect.Top, AItem.Caption);
390  end;
391end;
392
393procedure TCustomListView.CNDrawItem(var Message: TLMDrawListItem);
394var
395  State: TOwnerDrawState;
396  SaveIndex: Integer;
397begin
398  if Assigned(FCanvas) then
399  begin
400    with Message.DrawListItemStruct^ do
401    begin
402      State := ItemState;
403      SaveIndex := SaveDC(DC);
404      FCanvas.Lock;
405      try
406        FCanvas.Handle := DC;
407        FCanvas.Font := Font;
408        FCanvas.Brush := Brush;
409        if itemID = DWORD(-1) then
410          FCanvas.FillRect(Area)
411        else
412          DrawItem(Items[itemID], Area, State);
413      finally
414        FCanvas.Handle := 0;
415        FCanvas.Unlock;
416        RestoreDC(DC, SaveIndex);
417      end;
418    end;
419    Message.Result := 1;
420  end;
421end;
422
423procedure TCustomListView.InvalidateSelected;
424begin
425  FSelected:=nil;
426  FSelectedIdx := -1;
427  Exclude(FFlags,lffSelectedValid);
428end;
429
430procedure TCustomListView.HideEditor;
431var
432  S: String;
433begin
434  S := FEditor.Text;
435  if FEditor.Item <> nil then
436    DoEndEdit(FEditor.Item, S);
437  FEditor.Item := nil;
438  FEditor.Visible := False;
439  FEditor.SetBounds(0, 0, 0, 0);
440end;
441
442procedure TCustomListView.ShowEditor;
443var
444  Item: TListItem;
445  R: TRect;
446  TempHeight: Integer;
447  S: String;
448begin
449  if (ItemIndex >= 0) and (ItemIndex < Items.Count) then
450    Item := Items[ItemIndex]
451  else
452    Item := nil;
453  HideEditor;
454  if Item = nil then
455    exit;
456  if not CanEdit(Item) then
457    exit;
458
459  R := Item.DisplayRect(drLabel);
460  if LCLIntf.IsRectEmpty(R) then
461    exit;
462  S := Item.Caption;
463  if S = '' then
464    S := 'H';
465  TempHeight := Canvas.TextHeight(S);
466  if TempHeight >= R.Bottom - R.Top then
467    TempHeight := TempHeight - (R.Bottom - R.Top) + 4 {border above and below text}
468  else
469    TempHeight := 0;
470  with R do
471    FEditor.SetBounds(Left, Top, Right - Left, (Bottom - Top) + TempHeight);
472  FEditor.Item := Item;
473  FEditor.Text := Item.Caption;
474  FEditor.Visible := True;
475  FEditor.SetFocus;
476end;
477
478procedure TCustomListView.WMHScroll(var message: TLMHScroll);
479begin
480  if IsEditing then
481  begin
482    if FEditor.Focused then
483      SetFocus
484    else
485      HideEditor;
486  end;
487end;
488
489procedure TCustomListView.WMVScroll(var message: TLMVScroll);
490begin
491  if IsEditing then
492  begin
493    if FEditor.Focused then
494      SetFocus
495    else
496      HideEditor;
497  end;
498end;
499
500{------------------------------------------------------------------------------}
501{   TCustomListView IsCustomDrawn                                              }
502{------------------------------------------------------------------------------}
503function TCustomListView.IsCustomDrawn(ATarget: TCustomDrawTarget; AStage: TCustomDrawStage): Boolean;
504begin
505  case ATarget of
506    dtControl: Result := Assigned(FOnAdvancedCustomDraw)
507                      or Assigned(FOnAdvancedCustomDrawItem)
508                      or Assigned(FOnAdvancedCustomDrawSubItem);
509    dtItem:    Result := Assigned(FOnAdvancedCustomDrawItem)
510                      or Assigned(FOnAdvancedCustomDrawSubItem);
511    dtSubItem: Result := Assigned(FOnAdvancedCustomDrawSubItem);
512  end;
513
514  if Result then exit;
515
516  // check the normal events only in the prepaint stage
517  if AStage <> cdPrePaint then Exit;
518
519  case ATarget of
520    dtControl: Result := Assigned(FOnCustomDraw)
521                      or Assigned(FOnCustomDrawItem)
522                      or Assigned(FOnCustomDrawSubItem);
523    dtItem:    Result := Assigned(FOnCustomDrawItem)
524                      or Assigned(FOnCustomDrawSubItem);
525    dtSubItem: Result := Assigned(FOnCustomDrawSubItem);
526  end;
527end;
528
529{------------------------------------------------------------------------------}
530{   TCustomListView InitializeWnd                                              }
531{------------------------------------------------------------------------------}
532procedure TCustomListView.InitializeWnd;
533var
534  LVC: TWSCustomListViewClass;
535  lvil: TListViewImageList;
536begin
537  inherited InitializeWnd;
538
539  LVC := TWSCustomListViewClass(WidgetSetClass);
540
541  // set the style first
542  LVC.SetViewStyle(Self, FViewStyle);
543
544  // add columns
545  FColumns.WSCreateColumns;
546
547  // set imagelists and item depending properties
548  for lvil := Low(TListViewImageList) to High(TListViewImageList) do
549  begin
550    if FImages[lvil] <> nil
551    then LVC.SetImageList(Self, lvil, FImages[lvil].ResolutionForPPI[FImagesWidth[lvil], Font.PixelsPerInch, 1].Resolution); // to-do: support scaling factor
552  end;
553  LVC.SetScrollBars(Self, FScrollBars);
554  LVC.SetViewOrigin(Self, FViewOriginCache) ;
555  LVC.SetProperties(Self, FProperties);
556  LVC.SetSort(Self, FSortType, FSortColumn, FSortDirection);
557
558  // add items
559  if not OwnerData then
560  begin
561    FListItems.WSCreateItems;
562    // set other properties
563    LVC.SetAllocBy(Self, FAllocBy);
564  end
565  else
566  begin
567    LVC.SetOwnerData(Self, True);
568    LVC.SetItemsCount(Self, FListItems.Count);
569  end;
570
571  LVC.SetDefaultItemHeight(Self, FDefaultItemHeight);
572  LVC.SetHotTrackStyles(Self, FHotTrackStyles);
573  LVC.SetHoverTime(Self, FHoverTime);
574
575  if FSelected <> nil
576  then LVC.ItemSetState(Self, FSelected.Index, FSelected, lisSelected, True);
577  if FFocused <> nil
578  then LVC.ItemSetState(Self, FFocused.Index, FFocused, lisFocused, True);
579end;
580
581{------------------------------------------------------------------------------}
582{   TCustomListView DoDeletion                                                 }
583{------------------------------------------------------------------------------}
584procedure TCustomListView.DoDeletion(AItem: TListItem);
585begin
586  if not (TMethod(@Self.Delete).Code = Pointer(@TCustomListView.Delete)) then
587    //There is an override for Delete, so use that for Delphi compatibility. Issue #0038263
588    Delete(AItem)
589  else
590  //if you change the code below, make sure to change it in TCustomListView.Delete as well
591  if not (csDestroying in ComponentState) and Assigned(FOnDeletion) then
592    FOnDeletion(Self, AItem);
593end;
594
595{------------------------------------------------------------------------------}
596{   TCustomListView Delete                                                     }
597{------------------------------------------------------------------------------}
598procedure TCustomListView.Delete(AItem : TListItem);
599begin
600  {
601    In Delphi Delete is called in reaction to Items.Delete, but
602    if you call it directly it will also do the actual deletion and then call the OnDeletion handler
603    In that case we simply call Items.Delete and this will then call Delete again and in the second run
604    we call the OnDeletion handler.
605    Not 100% Delphi compatible, but more compatible then it was before. (BB)
606  }
607  //debugln(['TCustomListView.Delete: (lifDestroying in AItem.FFlags)=',(lifDestroying in AItem.FFlags)]);
608  if not (lifDestroying in AItem.FFlags) then
609  begin
610    AItem.Delete;
611    Exit;
612  end;
613  if not (csDestroying in ComponentState) and Assigned(FOnDeletion) then
614    FOnDeletion(Self, AItem);
615end;
616
617{------------------------------------------------------------------------------}
618{   TCustomListView DoInsert                                                   }
619{------------------------------------------------------------------------------}
620procedure TCustomListView.DoInsert(AItem: TListItem);
621begin
622  if not (TMethod(@Self.InsertItem).Code = Pointer(@TCUstomListView.InsertItem)) then
623    //There is an override for InsertItem, so use that for Delphi compatibility. Issue #0038263
624    InsertItem(AItem)
625  else
626    //if you change the code below, make sure to change it in TCustomListView.Delete as well
627    if Assigned(FOnInsert) then FOnInsert(Self, AItem);
628end;
629
630{------------------------------------------------------------------------------}
631{   TCustomListView InsertItem                                                 }
632{------------------------------------------------------------------------------}
633procedure TCustomListView.InsertItem(AItem : TListItem);
634begin
635  if Assigned(FOnInsert) then FOnInsert(Self, AItem);
636end;
637
638{------------------------------------------------------------------------------}
639{   TCustomListView DoItemChecked                                              }
640{------------------------------------------------------------------------------}
641procedure TCustomListView.DoItemChecked(AItem: TListItem);
642var
643  B: Boolean;
644begin
645  if (not HandleAllocated) or (csLoading in ComponentState) then exit;
646  B := TWSCustomListViewClass(WidgetSetClass).ItemGetChecked(Self,
647    AItem.Index, AItem);
648  if B <> AItem.GetCheckedInternal then
649  begin
650    AItem.Checked := B;
651    if Assigned(FOnItemChecked) then
652      FOnItemChecked(Self, AItem);
653  end;
654end;
655
656{------------------------------------------------------------------------------}
657{   TCustomListView DoSelectItem                                               }
658{------------------------------------------------------------------------------}
659procedure TCustomListView.DoSelectItem(AItem: TListItem; ASelected: Boolean);
660begin
661  if Assigned(AItem) then
662    AItem.Selected:=ASelected;
663  if Assigned(FOnSelectItem) and
664    ([lffItemsMoving, lffItemsSorting] * FFlags = []) then
665      FOnSelectItem(Self, AItem, ASelected);
666end;
667
668procedure TCustomListView.DoAutoAdjustLayout(
669  const AMode: TLayoutAdjustmentPolicy; const AXProportion, AYProportion: Double
670  );
671var
672  i: Integer;
673  C: TListColumn;
674  L: TListViewImageList;
675begin
676  inherited;
677
678  if AMode in [lapAutoAdjustWithoutHorizontalScrolling, lapAutoAdjustForDPI] then
679  begin
680    for i := ColumnCount - 1 downto 0 do
681    begin
682      C := Column[i];
683      C.MaxWidth := Round(C.MaxWidth * AXProportion);
684      C.MinWidth := Round(C.MinWidth * AXProportion);
685      C.Width := Round(C.Width * AXProportion);
686    end;
687
688    for L in TListViewImageList do
689      SetImageListWS(L);
690  end;
691end;
692
693procedure TCustomListView.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
694begin
695  inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
696  if AutoWidthLastColumn then
697    ResizeLastColumn;
698end;
699
700procedure TCustomListView.DoEndEdit(AItem: TListItem; const AValue: String);
701var
702  S: string;
703begin
704  if not (TMethod(@Self.Edit).Code = Pointer(@TCustomListView.Edit)) then
705    //There is an override for Edit, so use that for Delphi compatibility. Issue #0038263
706    Edit(AItem)
707  else
708  begin
709    //if you change the code below, make sure to change it in TCustomListView.Edit as well
710    S := AValue;
711    if Assigned(FOnEdited) then
712      FOnEdited(Self, AItem, S);
713    if AItem <> nil then
714      AItem.Caption := S;
715  end;
716end;
717
718procedure TCustomListView.Edit(AItem: TListItem);
719var
720  S: String;
721begin
722  //debugln(['TCustomListView.Edit: FEditor.Text=',FEditor.Text]);
723  S := FEditor.Text;
724  if Assigned(FOnEdited) then
725    FOnEdited(Self, AItem, S);
726  if AItem <> nil then
727    AItem.Caption := S;
728end;
729
730{------------------------------------------------------------------------------}
731{   TCustomListView ItemDeleted                                                }
732{------------------------------------------------------------------------------}
733procedure TCustomListView.ItemDeleted(const AItem: TListItem);  //called by TListItems
734begin
735  //DebugLn('TCustomListView.ItemDeleted ',dbgs(AItem),' FSelected=',dbgs(FSelected));
736  if FSelected = AItem then InvalidateSelected;
737  if FFocused = AItem then FFocused := nil;
738  DoDeletion(AItem);
739end;
740
741{------------------------------------------------------------------------------}
742{   TCustomListView ItemInserted                                               }
743{------------------------------------------------------------------------------}
744procedure TCustomListView.ItemInserted(const AItem: TListItem);
745begin
746  if csDestroying in Componentstate then Exit;
747  DoInsert(AItem);
748end;
749
750class procedure TCustomListView.WSRegisterClass;
751begin
752  RegisterPropertyToSkip(Self, 'ItemIndex', 'Property streamed in older Lazarus revision', '');
753  RegisterPropertyToSkip(Self, 'BevelKind', 'VCL compatibility property', '');
754  RegisterPropertyToSkip(TListItem, 'OverlayIndex', 'VCL compatibility property', '');
755  inherited WSRegisterClass;
756  RegisterCustomListView;
757end;
758
759class function TCustomListView.GetControlClassDefaultSize: TSize;
760begin
761  Result.CX := 250;
762  Result.CY := 150;
763end;
764
765{------------------------------------------------------------------------------}
766{   TCustomListView SetItems                                                   }
767{------------------------------------------------------------------------------}
768procedure TCustomListView.SetItems(const AValue : TListItems);
769begin
770end;
771
772{------------------------------------------------------------------------------}
773{   TCustomListView SetItemVisible                                             }
774{------------------------------------------------------------------------------}
775procedure TCustomListView.SetItemVisible(const AValue : TListItem;
776                                                     const APartialOK: Boolean);
777begin
778  if (not HandleAllocated) or (csLoading in ComponentState) then exit;
779  TWSCustomListViewClass(WidgetSetClass).ItemShow(
780                                        Self, AValue.Index, AValue, APartialOK);
781end;
782
783
784
785function TCustomListView.IntfCustomDraw(ATarget: TCustomDrawTarget; AStage: TCustomDrawStage; AItem, ASubItem: Integer; AState: TCustomDrawState; const ARect: PRect): TCustomDrawResult;
786begin
787  Result := [];
788
789  // in the prepaint stage, return the notifications we want
790  if AStage = cdPrePaint
791  then begin
792    case ATarget of
793      dtControl: begin
794        if IsCustomDrawn(dtItem, cdPrePaint) or IsCustomDrawn(dtItem, cdPreErase)
795        then Include(Result, cdrNotifyItemDraw);
796
797        if IsCustomDrawn(dtItem, cdPostPaint)
798        then Include(Result, cdrNotifyPostPaint);
799
800        if IsCustomDrawn(dtItem, cdPostErase)
801        then Include(Result, cdrNotifyPostErase);
802
803        if IsCustomDrawn(dtSubItem, cdPrePaint)
804        then Include(Result, cdrNotifySubitemDraw);
805      end;
806      dtItem: begin
807        if IsCustomDrawn(dtItem, cdPostPaint)
808        then Include(Result, cdrNotifyPostPaint);
809
810        if IsCustomDrawn(dtItem, cdPostErase)
811        then Include(Result, cdrNotifyPostErase);
812
813        if IsCustomDrawn(dtSubItem, cdPrePaint)
814        then Include(Result, cdrNotifySubitemDraw);
815      end;
816      dtSubItem: begin
817        if IsCustomDrawn(dtSubItem, cdPostPaint)
818        then Include(Result, cdrNotifyPostPaint);
819
820        if IsCustomDrawn(dtSubItem, cdPostErase)
821        then Include(Result, cdrNotifyPostErase);
822      end;
823    end;
824  end;
825
826  if not IsCustomDrawn(ATarget, AStage) then Exit;
827
828
829  case ATarget of
830    dtControl: if CustomDraw(ARect^, AStage) then Exit;
831    dtItem:    if CustomDrawItem(Items[AItem], AState, AStage) then Exit;
832    dtSubItem: if CustomDrawSubItem(Items[AItem], ASubItem, AState, AStage) then Exit;
833  end;
834
835  // if we are here, a custom step returned false, so no default drawing
836  if AStage = cdPrePaint
837  then Result := [cdrSkipDefault];
838end;
839
840function TCustomListView.GetUpdateCount: Integer;
841begin
842  Result := FUpdateCount;
843end;
844
845procedure TCustomListView.DoGetOwnerData(Item: TListItem);
846begin
847  if Assigned(OnData) then OnData(Self, Item);
848end;
849
850function TCustomListView.DoOwnerDataHint(AStartIndex, AEndIndex: Integer
851  ): Boolean;
852begin
853  Result := Assigned(FOnDataHint);
854  if Result then
855    FOnDataHint(Self, AStartIndex, AEndIndex);
856end;
857
858function TCustomListView.DoOwnerDataStateChange(AStartIndex,
859  AEndIndex: Integer; AOldState, ANewState: TListItemStates): Boolean;
860begin
861  Result := Assigned(FOnDataStateChange);
862  if Result then
863    FOnDataStateChange(Self, AStartIndex, AEndIndex, AOldState, ANewState);
864end;
865
866procedure TCustomListView.QueuedShowEditor(Data: PtrInt);
867begin
868  if fShowEditorQueued and IsVisible then
869  begin
870    fShowEditorQueued:=false;
871    ShowEditor;
872  end;
873end;
874
875procedure TCustomListView.DblClick;
876begin
877  inherited DblClick;
878  if not ReadOnly and Assigned(FEditor) then
879    ShowEditorQueued:=true;
880end;
881
882procedure TCustomListView.KeyDown(var Key: Word; Shift: TShiftState);
883begin
884  if not ReadOnly and (Key = VK_F2) and (Shift = []) then
885  begin
886    ShowEditor;
887    Key := 0;
888  end else
889    inherited KeyDown(Key, Shift);
890end;
891
892{------------------------------------------------------------------------------}
893{   TCustomListView SetColumns                                                 }
894{------------------------------------------------------------------------------}
895procedure TCustomListView.SetColumns(const AValue: TListColumns);
896begin
897  if AValue=FColumns then exit;
898  BeginUpdate;
899  FColumns.Assign(AValue);
900  EndUpdate;
901  if ([csDesigning,csLoading,csReading]*ComponentState=[csDesigning]) then
902    OwnerFormDesignerModified(Self);
903end;
904
905
906{------------------------------------------------------------------------------}
907{   TCustomListView SetViewOrigin                                              }
908{------------------------------------------------------------------------------}
909procedure TCustomListView.SetViewOrigin(AValue: TPoint);
910begin
911  if AValue.X < 0 then AValue.X := 0;
912  if AValue.Y < 0 then AValue.Y := 0;
913  if HandleAllocated
914  then begin
915    TWSCustomListViewClass(WidgetSetClass).SetViewOrigin(Self, AValue);
916  end
917  else begin
918    FViewOriginCache := AValue;
919  end;
920end;
921
922{------------------------------------------------------------------------------}
923{   TCustomListView SetViewStyle                                               }
924{------------------------------------------------------------------------------}
925procedure TCustomListView.SetViewStyle(const Avalue: TViewStyle);
926begin
927  if FViewStyle = AValue then Exit;
928  FViewStyle := AValue;
929  if not HandleAllocated then Exit;
930  TWSCustomListViewClass(WidgetSetClass).SetViewStyle(Self, AValue);
931end;
932
933{------------------------------------------------------------------------------}
934{   TCustomListView SetSortType                                                }
935{------------------------------------------------------------------------------}
936procedure TCustomListView.SetSortType(const AValue: TSortType);
937begin
938  if FSortType = AValue then Exit;
939  FSortType := AValue;
940  Sort;
941end;
942
943{------------------------------------------------------------------------------}
944{   TCustomListView SetSortColumn                                              }
945{------------------------------------------------------------------------------}
946procedure TCustomListView.SetSortColumn(const AValue : Integer);
947begin
948  if FSortColumn = AValue then Exit;
949  FSortColumn := AValue;
950  Sort;
951end;
952
953procedure TCustomListView.SetSortDirection(const AValue: TSortDirection);
954begin
955  if FSortDirection=AValue then exit;
956  FSortDirection:=AValue;
957  Sort;
958end;
959
960function _CompareListViewItems_CustomSort(Item1, Item2: Pointer): Integer;
961var
962  ListView: TCustomListView;
963begin
964  Result := 0;
965  ListView := TListItem(Item1).Owner.Owner;
966
967  if Assigned(ListView.FCustomSort_Func) then
968    Result := ListView.FCustomSort_Func(TListItem(Item1), TListItem(Item2), ListView.FCustomSort_Param)
969  else
970  if Assigned(ListView.FOnCompare) then
971    ListView.FOnCompare(ListView, TListItem(Item1), TListItem(Item2), 0, Result);
972end;
973
974function _CompareListViewItems(Item1, Item2: Pointer): Integer;
975var
976  Str1: String;
977  Str2: String;
978  ListView: TCustomListView;
979begin
980  Result := 0;
981  ListView := TListItem(Item1).Owner.Owner;
982  if Assigned(ListView.FOnCompare) then
983    ListView.FOnCompare(ListView, TListItem(Item1), TListItem(Item2),0 ,Result)
984  else
985  begin
986    if ListView.SortType in [stData] then
987      Result := CompareValue(PtrUInt(TListItem(Item1).Data), PtrUInt(TListItem(Item2).Data))
988    else
989    begin
990      if ListView.FSortColumn = 0 then
991      begin
992        Str1 := TListItem(Item1).Caption;
993        Str2 := TListItem(Item2).Caption;
994      end else
995      begin
996        if ListView.FSortColumn <= TListItem(Item1).SubItems.Count then
997          Str1 := TListItem(Item1).SubItems.Strings[ListView.FSortColumn-1]
998        else
999          Str1 := '';
1000        if ListView.FSortColumn <= TListItem(Item2).SubItems.Count then
1001          Str2 := TListItem(Item2).SubItems.Strings[ListView.FSortColumn-1]
1002        else
1003          Str2 := '';
1004      end;
1005      Result := AnsiCompareText(Str1, Str2);
1006    end;
1007    if ListView.SortDirection = sdDescending then
1008      Result := -Result;
1009  end;
1010end;
1011
1012{------------------------------------------------------------------------------}
1013{   TCustomListView Sort                                                       }
1014{------------------------------------------------------------------------------}
1015procedure TCustomListView.Sort;
1016begin
1017  if FSortType = stNone then exit;
1018  if (FSortColumn < 0) or (FSortColumn >= ColumnCount) then exit;
1019  SortWithParams(@_CompareListViewItems);
1020end;
1021
1022function TCustomListView.CustomSort(ASortProc: TLVCompare; AOptionalParam: PtrInt): Boolean;
1023begin
1024  Result := Assigned(ASortProc) or Assigned(FOnCompare);
1025  if Result then
1026  begin
1027    FCustomSort_Func := ASortProc;
1028    FCustomSort_Param := AOptionalParam;
1029    SortWithParams(@_CompareListViewItems_CustomSort);
1030  end;
1031end;
1032
1033procedure TCustomListView.SortWithParams(ACompareFunc: TListSortCompare);
1034var
1035  FSavedSelection: TFPList;
1036  FSavedFocused: TListItem;
1037  FSavedChecked: TFPList;
1038  FSavedCheckItem: TListItem;
1039  i: Integer;
1040  AItemIndex: Integer;
1041begin
1042  if FListItems.Count < 2 then exit;
1043  if lffPreparingSorting in FFlags then exit;
1044
1045  if HandleAllocated then
1046  begin
1047    Include(FFlags, lffItemsSorting);
1048    FSavedSelection := TFPList.Create;
1049    FSavedChecked := nil;
1050    try
1051      if (ItemIndex >= 0) then
1052        FSavedFocused := Items[ItemIndex]
1053      else
1054        FSavedFocused := nil;
1055      if Assigned(Selected) then
1056      begin
1057        FSavedSelection.Add(Selected);
1058        if MultiSelect then
1059          for i := 0 to Items.Count-1 do
1060            if Items[i].Selected and (Items[i] <> Selected) then
1061              FSavedSelection.Add(Items[i]);
1062      end;
1063      if (TWSCustomListViewClass(WidgetSetClass).RestoreItemCheckedAfterSort(Self))
1064        and Items.WSUpdateAllowed
1065        and not OwnerData then
1066      begin
1067        FSavedChecked := TFPList.Create;
1068        for i := 0 to Items.Count - 1 do
1069          if Items[i].Checked then
1070            FSavedChecked.Add(Items[i]);
1071      end;
1072
1073      Items.FCacheIndex := -1;
1074      Items.FCacheItem := nil;
1075
1076      FListItems.FItems.Sort(ACompareFunc);
1077      TWSCustomListViewClass(WidgetSetClass).SetSort(Self, FSortType,
1078        FSortColumn, FSortDirection);
1079
1080      if (FSavedSelection.Count > 0) or Assigned(FSavedFocused) then
1081      begin
1082        Selected := nil; // unselect all
1083
1084        if FSavedFocused <> nil then
1085          TWSCustomListViewClass(WidgetSetClass).ItemSetState(Self,
1086            FSavedFocused.Index, FSavedFocused, lisFocused, True);
1087
1088        for i := FSavedSelection.Count - 1 downto 0 do
1089        begin
1090          AItemIndex := Items.IndexOf(TListItem(FSavedSelection.Items[i]));
1091          if AItemIndex <> -1 then
1092            TWSCustomListViewClass(WidgetSetClass).ItemSetState(Self,
1093              AItemIndex, Items[AItemIndex], lisSelected, True);
1094        end;
1095
1096        if FSavedChecked <> nil then
1097        begin
1098          for i := 0 to FSavedChecked.Count - 1 do
1099          begin
1100            FSavedCheckItem := TListItem( FSavedChecked[i] );
1101            // todo: this is inefficient, because FSavedCheckItem.Index must be called again
1102            TWSCustomListViewClass(WidgetSetClass).ItemSetChecked(Self,
1103              FSavedCheckItem.Index, FSavedCheckItem, True);
1104          end;
1105          FSavedChecked.Free;
1106        end;
1107
1108      end;
1109    finally
1110      FreeThenNil(FSavedSelection);
1111      Exclude(FFlags, lffItemsSorting);
1112    end;
1113  end else
1114    FListItems.FItems.Sort(ACompareFunc);
1115end;
1116
1117{------------------------------------------------------------------------------}
1118{   TCustomListView Destructor                                                 }
1119{------------------------------------------------------------------------------}
1120destructor TCustomListView.Destroy;
1121var
1122  lvil: TListViewImageList;
1123begin
1124  Application.RemoveAsyncCalls(Self);
1125  // Better destroy the wincontrol (=widget) first. So wo don't have to delete
1126  // all items/columns and we won't get notifications for each.
1127  FreeAndNil(FCanvas);
1128  inherited Destroy;
1129  FreeAndNil(FColumns);
1130  for lvil := Low(TListViewImageList) to High(TListViewImageList) do
1131    FreeAndNil(FImageChangeLinks[lvil]);
1132  FreeAndNil(FOwnerDataItem);
1133  FreeAndNil(FListItems);
1134  FreeAndNil(FIconOptions);
1135end;
1136
1137procedure TCustomListView.AddItem(Item: string; AObject: TObject);
1138var
1139  AItem: TListItem;
1140begin
1141  AItem := Items.Add;
1142  AItem.Caption := Item;
1143  AItem.Data := AObject;
1144end;
1145
1146function TCustomListView.AlphaSort: Boolean;
1147begin
1148  Result := False;
1149  Include(FFlags, lffPreparingSorting);
1150  // always reset direction, so sort triggers later !
1151  SortDirection := sdDescending;
1152  SortType := stText;
1153  SortColumn := 0;
1154  Exclude(FFlags, lffPreparingSorting);
1155  // now trigger sort when all rules are applied
1156  SortDirection := sdAscending;
1157  Result := True;
1158end;
1159
1160{------------------------------------------------------------------------------
1161   TCustomListView DestroyWnd
1162   Params: None
1163   Result: none
1164
1165   Frees the canvas
1166 ------------------------------------------------------------------------------}
1167procedure TCustomListView.DestroyWnd;
1168begin
1169  if FCanvas<>nil then
1170    TControlCanvas(FCanvas).FreeHandle;
1171  inherited DestroyWnd;
1172end;
1173
1174procedure TCustomListView.BeginAutoDrag;
1175begin
1176  BeginDrag(False);
1177end;
1178
1179function TCustomListView.CreateListItem: TListItem;
1180var
1181  AItemClass: TListItemClass;
1182begin
1183  AItemClass := TListItem;
1184  if Assigned(OnCreateItemClass) then
1185    OnCreateItemClass(Self, AItemClass);
1186  Result := AItemClass.Create(Items);
1187end;
1188
1189function TCustomListView.CreateListItems: TListItems;
1190begin
1191  Result := TListItems.Create(Self);
1192end;
1193
1194{------------------------------------------------------------------------------
1195   TCustomListView BeginUpdate
1196   Params: None
1197   Result: none
1198
1199   Increases the update count. Use this procedure before any big change, so that
1200   the interface will not show any single step.
1201 ------------------------------------------------------------------------------}
1202procedure TCustomListView.BeginUpdate;
1203begin
1204  Inc(FUpdateCount);
1205  Include(FFlags, lffPreparingSorting);
1206  if (FUpdateCount = 1) and HandleAllocated
1207  then TWSCustomListViewClass(WidgetSetClass).BeginUpdate(Self);
1208end;
1209
1210function TCustomListView.CanEdit(Item: TListItem): Boolean;
1211begin
1212  Result := True;
1213  if Assigned(FOnEditing) then
1214    FOnEditing(Self, Item, Result);
1215end;
1216
1217procedure TCustomListView.Clear;
1218begin
1219  FListItems.Clear;
1220end;
1221
1222{------------------------------------------------------------------------------}
1223{   TCustomListView EndUpdate                                               }
1224{------------------------------------------------------------------------------}
1225procedure TCustomListView.EndUpdate;
1226begin
1227  if FUpdateCount <= 0
1228  then RaiseGDBException('TCustomListView.EndUpdate FUpdateCount=0');
1229
1230  Dec(FUpdateCount);
1231  if FUpdateCount=0 then Exclude(FFlags, lffPreparingSorting);
1232  if (FUpdateCount = 0) and HandleAllocated
1233  then TWSCustomListViewClass(WidgetSetClass).EndUpdate(Self);
1234end;
1235
1236procedure TCustomListView.Repaint;
1237begin
1238  if OwnerData then
1239    // the last cached item might be left updated, because OnData isn't called!
1240    FOwnerDataItem.SetDataIndex(-1);
1241  inherited Repaint;
1242end;
1243
1244procedure TCustomListView.FinalizeWnd;
1245begin
1246  FShowEditorQueued:=false;
1247  // store origin
1248  FViewOriginCache := TWSCustomListViewClass(WidgetSetClass).GetViewOrigin(Self);
1249  if not OwnerData then
1250    FListItems.DoFinalizeWnd;
1251  Columns.DoFinalizeWnd;
1252  inherited FinalizeWnd;
1253end;
1254
1255function TCustomListView.FindCaption(StartIndex: Integer; Value: string;
1256  Partial, Inclusive, Wrap: Boolean; PartStart: Boolean = True): TListItem;
1257begin
1258  Result := FListItems.FindCaption(StartIndex, Value, Partial, Inclusive, Wrap, PartStart);
1259end;
1260
1261function TCustomListView.FindData(StartIndex: Integer; Value: Pointer;
1262  Inclusive, Wrap: Boolean): TListItem;
1263begin
1264  Result := FListItems.FindData(StartIndex, Value, Inclusive, Wrap);
1265end;
1266
1267function TCustomListView.GetBoundingRect: TRect;
1268begin
1269  if not HandleAllocated
1270  then Result := Rect(0,0,0,0)
1271  else Result := TWSCustomListViewClass(WidgetSetClass).GetBoundingRect(Self);
1272end;
1273
1274function TCustomListView.GetColumnCount: Integer;
1275begin
1276  Result := FColumns.Count;
1277end;
1278
1279function TCustomListView.GetColumnFromIndex(AIndex: Integer): TListColumn;
1280begin
1281  Result := FColumns[AIndex];
1282end;
1283
1284function TCustomListView.GetDropTarget: TListItem;
1285var
1286  idx: Integer;
1287begin
1288  if not HandleAllocated
1289  then idx := -1
1290  else idx := TWSCustomListViewClass(WidgetSetClass).GetDropTarget(Self);
1291  if idx = -1
1292  then Result := nil
1293  else Result := FListItems[idx];
1294end;
1295
1296function TCustomListView.GetFocused: TListItem;
1297begin
1298  Result := FFocused;
1299end;
1300
1301function TCustomListView.GetImageList(const ALvilOrd: Integer): TCustomImageList;
1302begin
1303  Result := FImages[TListViewImageList(ALvilOrd)];
1304end;
1305
1306function TCustomListView.GetImageListWidth(const ALvilOrd: Integer): Integer;
1307begin
1308  Result := FImagesWidth[TListViewImageList(ALvilOrd)];
1309end;
1310
1311function TCustomListView.GetHoverTime: Integer;
1312begin
1313  if HandleAllocated
1314  then Result := TWSCustomListViewClass(WidgetSetClass).GetHoverTime(Self)
1315  else Result := FHoverTime;
1316end;
1317
1318function TCustomListView.GetItemIndex: Integer;
1319begin
1320  Result := -1;
1321  if not OwnerData then begin
1322    if Selected = nil then Exit;
1323    Result := Selected.Index
1324  end else
1325    Result := FSelectedIdx;
1326end;
1327
1328function TCustomListView.GetHitTestInfoAt(X, Y: Integer): THitTests;
1329begin
1330  Result := [];
1331  if HandleAllocated then
1332    Result := TWSCustomListViewClass(WidgetSetClass).GetHitTestInfoAt( Self, X, Y );
1333end;
1334
1335function TCustomListView.GetItemAt(x, y: integer): TListItem;
1336var
1337  Item: Integer;
1338begin
1339  Result := nil;
1340  if HandleAllocated
1341  then begin
1342    Item := TWSCustomListViewClass(WidgetSetClass).GetItemAt(Self,x,y);
1343    if Item <> -1
1344    then Result := Items[Item];
1345  end;
1346end;
1347
1348function TCustomListView.GetNearestItem(APoint: TPoint;
1349  Direction: TSearchDirection): TListItem;
1350var
1351  AItem: TListItem;
1352  AIndex: Integer;
1353begin
1354  Result := nil;
1355  AItem := GetItemAt(APoint.x, APoint.y);
1356  if Assigned(AItem) then
1357  begin
1358    AIndex := AItem.Index;
1359    case Direction of
1360      sdAbove: if AIndex - 1 >= 0 then Result := Items[AIndex - 1];
1361      sdBelow: if AIndex - 1 < Items.Count then
1362        Result := Items[AIndex + 1];
1363    end;
1364  end;
1365end;
1366
1367function TCustomListView.GetNextItem(StartItem: TListItem;
1368  Direction: TSearchDirection; States: TListItemStates): TListItem;
1369begin
1370  Result := nil;
1371  if HandleAllocated then
1372     Result := TWSCustomListViewClass(WidgetSetClass).GetNextItem(Self, StartItem, Direction, States);
1373end;
1374
1375procedure TCustomListView.ClearSelection;
1376begin
1377  Self.BeginUpdate;
1378  if MultiSelect then
1379  begin
1380    if HandleAllocated then
1381      TWSCustomListViewClass(WidgetSetClass).SelectAll(Self, False);
1382    Items.ClearSelection;
1383  end else
1384  if (ItemIndex >= 0) and (ItemIndex < Items.Count) then
1385    Items.Item[ItemIndex].Selected := False;
1386  Self.EndUpdate;
1387end;
1388
1389procedure TCustomListView.SelectAll;
1390begin
1391  if not MultiSelect then
1392    exit;
1393  Self.BeginUpdate;
1394  if HandleAllocated then
1395    TWSCustomListViewClass(WidgetSetClass).SelectAll(Self, True);
1396  Items.SelectAll;
1397  Self.EndUpdate;
1398end;
1399
1400function TCustomListView.IsEditing: Boolean;
1401begin
1402  Result := Assigned(Self.FEditor) and FEditor.Visible;
1403end;
1404
1405function TCustomListView.GetProperty(const ALvpOrd: Integer): Boolean;
1406begin
1407  Result := (TListViewProperty(ALvpOrd) in FProperties);
1408end;
1409
1410function TCustomListView.GetSelCount: Integer;
1411var
1412  i: integer;
1413begin
1414  if HandleAllocated
1415  then Result := TWSCustomListViewClass(WidgetSetClass).GetSelCount(Self)
1416  else
1417  begin
1418    Result := 0;
1419    for i := 0 to Items.Count - 1 do
1420      if Items[i].Selected then
1421        inc(Result);
1422  end;
1423end;
1424
1425{------------------------------------------------------------------------------
1426   TCustomListView GetSelection
1427------------------------------------------------------------------------------}
1428function TCustomListView.GetSelection: TListItem;
1429var
1430  i: Integer;
1431begin
1432  if OwnerData and not MultiSelect then
1433  begin
1434    if FSelectedIdx>=0 then begin
1435      FOwnerDataItem.SetDataIndex(FSelectedIdx);
1436      Result:=FOwnerDataItem;
1437    end else
1438      Result:=nil;
1439  end
1440  else begin
1441    { according to Delphi docs we always must return first selected item,
1442      not the last selected one see issue #16773 }
1443    if not (lffSelectedValid in FFlags) or MultiSelect then
1444    begin
1445      FSelected := nil;
1446      for i := 0 to Items.Count - 1 do
1447      begin
1448        if Items[i].Selected then
1449        begin
1450          FSelected := Items[i];
1451          break;
1452        end;
1453      end;
1454      Include(FFlags, lffSelectedValid);
1455    end;
1456    Result := FSelected;
1457  end;
1458end;
1459
1460function TCustomListView.GetTopItem: TListItem;
1461var
1462  idx: Integer;
1463begin
1464  if ViewStyle in [vsSmallIcon, vsIcon]
1465  then idx := -1
1466  else idx := TWSCustomListViewClass(WidgetSetClass).GetTopItem(Self);
1467  if idx = -1
1468  then Result := nil
1469  else Result := FListItems[idx];
1470end;
1471
1472{------------------------------------------------------------------------------}
1473{   TCustomListView GetViewOrigin                                              }
1474{------------------------------------------------------------------------------}
1475function TCustomListView.GetViewOrigin: TPoint;
1476begin
1477  if HandleAllocated
1478  then begin
1479    Result := TWSCustomListViewClass(WidgetSetClass).GetViewOrigin(Self);
1480  end
1481  else begin
1482    Result := FViewOriginCache;
1483  end;
1484end;
1485
1486
1487function TCustomListView.GetVisibleRowCount: Integer;
1488begin
1489  if ViewStyle in [vsReport, vsList]
1490  then Result := TWSCustomListViewClass(WidgetSetClass).GetVisibleRowCount(Self)
1491  else Result := 0;
1492end;
1493
1494procedure TCustomListView.SetAllocBy(const AValue: Integer);
1495begin
1496  if FAllocBy = AValue then Exit;
1497  FAllocBy := AValue;
1498  if not HandleAllocated then Exit;
1499  TWSCustomListViewClass(WidgetSetClass).SetAllocBy(Self, AValue);
1500end;
1501
1502procedure TCustomListView.ResizeLastColumn;
1503var
1504  i: Integer;
1505  LastVisibleColumn: Integer;
1506  Accu: Integer;
1507  W: Integer;
1508  NewWidth: Integer;
1509begin
1510  if not (ViewStyle in [vsList, vsReport]) or (ColumnCount = 0) then
1511    exit;
1512  LastVisibleColumn := -1;
1513
1514  // find last visible column
1515  for i := ColumnCount - 1 downto 0 do
1516  begin
1517    if Column[i].Visible then
1518    begin
1519      LastVisibleColumn := i;
1520      break;
1521    end;
1522  end;
1523
1524  // calculate size and apply it only if it's > 0
1525  if LastVisibleColumn >= 0 then
1526  begin
1527    //TODO: gtk2 doesnt return correct ClientWidth. win32 and qt works ok.
1528    W := ClientWidth - (BorderWidth * 2);
1529    Accu := 0;
1530    for i := 0 to LastVisibleColumn - 1 do
1531    begin
1532      if Column[i].Visible then
1533        Accu := Accu + Column[i].Width;
1534    end;
1535    NewWidth := W - Accu;
1536    if NewWidth > 0 then
1537    begin
1538      // now set AutoSize and MinWidth/MaxWidth to 0
1539      Column[LastVisibleColumn].AutoSize := False;
1540      Column[LastVisibleColumn].MinWidth := 0;
1541      Column[LastVisibleColumn].MaxWidth := 0;
1542      Column[LastVisibleColumn].Width := NewWidth;
1543    end;
1544  end;
1545end;
1546
1547procedure TCustomListView.SetAutoWidthLastColumn(AValue: Boolean);
1548begin
1549  if FAutoWidthLastColumn=AValue then Exit;
1550  FAutoWidthLastColumn:=AValue;
1551  if FAutoWidthLastColumn then
1552    ResizeLastColumn;
1553end;
1554
1555procedure TCustomListView.SetDefaultItemHeight(AValue: Integer);
1556begin
1557  if AValue <=0 then AValue := 20;
1558  if AValue = FDefaultItemHeight then Exit;
1559  FDefaultItemHeight := AValue;
1560  if not HandleAllocated then Exit;
1561  TWSCustomListViewClass(WidgetSetClass).SetDefaultItemHeight(Self, AValue);
1562end;
1563
1564procedure TCustomListView.SetDropTarget(const AValue: TListItem);
1565begin
1566  if not HandleAllocated then Exit;
1567  TWSCustomListViewClass(WidgetSetClass).ItemSetState(Self, AValue.Index, AValue, lisDropTarget, True);
1568end;
1569
1570procedure TCustomListView.SetFocused(const AValue: TListItem);
1571begin
1572  if FFocused = AValue then exit;
1573  FFocused := AValue;
1574  if not HandleAllocated then
1575    Exit;
1576  if FFocused <> nil then
1577    TWSCustomListViewClass(WidgetSetClass).ItemSetState(Self, FFocused.Index, FFocused, lisFocused, True);
1578end;
1579
1580procedure TCustomListView.SetHotTrackStyles(const AValue: TListHotTrackStyles);
1581begin
1582  if FHotTrackStyles = AValue then Exit;
1583  FHotTrackStyles := AValue;
1584  if not HandleAllocated then Exit;
1585  TWSCustomListViewClass(WidgetSetClass).SetHotTrackStyles(Self, AValue);
1586end;
1587
1588procedure TCustomListView.SetHoverTime(const AValue: Integer);
1589begin
1590  if FHoverTime = AValue then Exit;
1591  FHoverTime := AValue;
1592  if not HandleAllocated then Exit;
1593  TWSCustomListViewClass(WidgetSetClass).SetHoverTime(Self, FHoverTime);
1594end;
1595
1596procedure TCustomListView.SetIconOptions(const AValue: TIconOptions);
1597begin
1598  FIconOptions.Assign(AValue);
1599end;
1600
1601procedure TCustomListView.SetImageList(const ALvilOrd: Integer; const AValue: TCustomImageList);
1602var
1603  lvil: TListViewImageList;
1604begin
1605  lvil := TListViewImageList(ALvilOrd);
1606
1607  if FImages[lvil] = AValue then Exit;
1608
1609  if FImages[lvil] <> nil
1610  then FImages[lvil].UnregisterChanges(FImageChangeLinks[lvil]);
1611
1612  FImages[lvil] := AValue;
1613
1614  if FImages[lvil] <> nil
1615  then begin
1616    FImages[lvil].RegisterChanges(FImageChangeLinks[lvil]);
1617    FImages[lvil].FreeNotification(self);
1618  end;
1619
1620  SetImageListWS(lvil);
1621end;
1622
1623procedure TCustomListView.SetImageListAsync(Data: PtrInt);
1624begin
1625  SetImageListWS(TListViewImageList(Data));
1626end;
1627
1628procedure TCustomListView.SetImageListWidth(const ALvilOrd: Integer;
1629  const AValue: Integer);
1630var
1631  lvil: TListViewImageList;
1632begin
1633  lvil := TListViewImageList(ALvilOrd);
1634
1635  if FImagesWidth[lvil] = AValue then Exit;
1636
1637  FImagesWidth[lvil] := AValue;
1638
1639  SetImageListWS(lvil);
1640end;
1641
1642procedure TCustomListView.SetImageListWS(const ALvil: TListViewImageList);
1643var
1644  R: TCustomImageListResolution;
1645begin
1646  if not HandleAllocated then
1647    Exit;
1648  if FImages[ALvil]<>nil then
1649    R := FImages[ALvil].ResolutionForPPI[FImagesWidth[ALvil], Font.PixelsPerInch, 1].Resolution // to-do: support scaling factor
1650  else
1651    R := nil;
1652  TWSCustomListViewClass(WidgetSetClass).SetImageList(Self, ALvil, R)
1653end;
1654
1655procedure TCustomListView.SetItemIndex(const AValue: Integer);
1656begin
1657  if (AValue < -1) or (AValue >= Items.Count) then
1658    raise Exception.CreateFmt(rsListIndexExceedsBounds,[AValue]);
1659
1660  if AValue = -1 then
1661    Selected := nil
1662  else
1663  begin
1664    // trigger ws selection update, it'll update Selected too
1665    if OwnerData then
1666    begin
1667      // clean selection when itemindex is changed. issue #19825
1668      if MultiSelect then
1669        Selected := nil;
1670      FSelectedIdx := AValue;
1671      Items.Item[AValue].Selected := True;
1672    end else
1673      Selected := Items.Item[AValue];
1674  end;
1675end;
1676
1677{------------------------------------------------------------------------------
1678   TCustomListView SetSelection
1679------------------------------------------------------------------------------}
1680procedure TCustomListView.SetSelection(const AValue: TListItem);
1681var
1682  i: integer;
1683begin
1684  if (AValue<>nil) and (AValue.ListView<>Self) then
1685    raise Exception.Create('Item does not belong to this listview');
1686  if (not FOwnerData) and (FSelected = AValue) then Exit;
1687  //DebugLn('TCustomListView.SetSelection FSelected=',dbgs(FSelected));
1688  if AValue = nil then
1689  begin
1690    if MultiSelect then
1691    begin
1692      BeginUpdate;
1693      try
1694        for i:=0 to Items.Count-1 do
1695          with Items[i] do
1696            if Selected then
1697              Selected:=False;
1698      finally
1699        EndUpdate;
1700      end;
1701    end else
1702    if FSelected <> nil then
1703      FSelected.Selected := False;
1704    FSelected := nil;
1705    Include(FFlags,lffSelectedValid);
1706  end else
1707  begin
1708    FSelected := AValue;
1709    if HandleAllocated then
1710      TWSCustomListViewClass(WidgetSetClass).ItemSetState(Self, FSelected.Index,
1711          FSelected, lisSelected, True);
1712  end;
1713end;
1714
1715procedure TCustomListView.SetShowEditorQueued(AValue: boolean);
1716begin
1717  if FShowEditorQueued=AValue then Exit;
1718  FShowEditorQueued:=AValue;
1719  if FShowEditorQueued then
1720    Application.QueueAsyncCall(@QueuedShowEditor,0);
1721end;
1722
1723procedure TCustomListView.SetOwnerData(const AValue: Boolean);
1724begin
1725  if FOwnerData=AValue then exit;
1726  FOwnerData:=AValue;
1727  FOwnerDataItem.SetOwner(nil);
1728  Items.Free;
1729  if AValue then
1730  begin
1731    FSelectedIdx := -1;
1732    FListItems := TOwnerDataListItems.Create(Self);
1733  end
1734  else
1735    FListItems := CreateListItems;
1736
1737  if HandleAllocated then
1738    TWSCustomListViewClass(WidgetSetClass).SetOwnerData(Self, AValue);
1739  FOwnerDataItem.SetOwner(FListItems);
1740end;
1741
1742procedure TCustomListView.SetProperty(const ALvpOrd: Integer;
1743  const AIsSet: Boolean);
1744var
1745  AProp: TListViewProperty;
1746begin
1747  AProp := TListViewProperty(ALvpOrd);
1748  if (AProp in FProperties) = AIsSet then Exit;
1749
1750  if AIsSet
1751  then Include(FProperties, AProp)
1752  else Exclude(FProperties, AProp);
1753
1754  if not HandleAllocated then Exit;
1755  TWSCustomListViewClass(WidgetSetClass).SetProperty(Self, AProp, AIsSet);
1756end;
1757
1758procedure TCustomListView.ImageChanged(Sender : TObject);
1759begin
1760  if csDestroying in ComponentState Then Exit;
1761// TODO: move Imagelist to interface, image changes can be update there
1762//  if FUpdateCount>0 then
1763//    Include(FStates,lvUpdateNeeded)
1764//  else begin
1765//    //image changed so redraw it all....
1766//    UpdateProperties;
1767//  end;
1768end;
1769
1770procedure TCustomListView.ImageResolutionHandleDestroyed(
1771  Sender: TCustomImageList; AWidth: Integer; AReferenceHandle: TLCLHandle);
1772var
1773  lvil: TListViewImageList;
1774begin
1775  if not HandleAllocated then
1776    Exit;
1777
1778  for lvil in TListViewImageList do
1779    if Sender = FImages[lvil] then
1780    begin
1781      TWSCustomListViewClass(WidgetSetClass).SetImageList(Self, lvil, nil);
1782      Application.QueueAsyncCall(@SetImageListAsync, Ord(lvil));
1783    end;
1784end;
1785
1786procedure TCustomListView.Loaded;
1787begin
1788  // create interface columns if needed
1789  if HandleAllocated then
1790    FColumns.WSCreateColumns;
1791  inherited Loaded;
1792end;
1793
1794procedure TCustomListView.Notification(AComponent: TComponent; Operation: TOperation);
1795begin
1796  inherited Notification(AComponent, Operation);
1797  if Operation = opRemove then begin
1798    if AComponent = LargeImages then LargeImages := nil;
1799    if AComponent = SmallImages then SmallImages := nil;
1800    if AComponent = StateImages then StateImages := nil;
1801  end;
1802end;
1803
1804procedure TCustomListView.SetScrollBars(const AValue: TScrollStyle);
1805begin
1806  if (FScrollBars = AValue) then exit;
1807  FScrollBars := AValue;
1808  if not HandleAllocated then Exit;
1809  TWSCustomListViewClass(WidgetSetClass).SetScrollBars(Self, AValue);
1810  UpdateScrollBars;
1811end;
1812
1813procedure TCustomListView.UpdateScrollbars;
1814begin
1815  // this needs to be done in the widgetset
1816  DebugLn('TODO: TCustomListView.UpdateScrollbars');
1817  exit;
1818
1819  if not HandleAllocated then exit;
1820end;
1821
1822