1{%MainUnit ../comboex.pas}
2
3{*****************************************************************************
4                     TCustomComboBoxEx, TCustomCheckComboBox
5 *****************************************************************************
6
7 *****************************************************************************
8  See the file COPYING.modifiedLGPL.txt, included in this distribution,
9  for details about the license.
10 *****************************************************************************
11}
12
13{ TListControlItem }
14
15constructor TListControlItem.Create(ACollection: TCollection);
16begin
17  inherited Create(ACollection);
18  FImageIndex:=-1;
19end;
20
21{ TListControlItem.Setters }
22
23procedure TListControlItem.SetCaption(const AValue: TTranslateString);
24begin
25  if FCaption=AValue then exit;
26  FCaption:=AValue;
27  Changed(False);
28end;
29
30procedure TListControlItem.SetImageIndex(AValue: TImageIndex);
31begin
32  if FImageIndex=AValue then exit;
33  FImageIndex:=AValue;
34  Changed(False);
35end;
36
37{ TComboExItem }
38
39constructor TComboExItem.Create(ACollection: TCollection);
40begin
41  inherited Create(ACollection);
42  FIndent:=-1;
43  FOverlayImageIndex:=-1;
44  FSelectedImageIndex:=-1;
45end;
46
47destructor TComboExItem.Destroy;
48begin
49  { normally, Items.Count should be already MenuItems.Count-1 ATM }
50  { this solves case when item is not deleted via Collection.Delete(Index) }
51  { but directly via Item.Free (exactly what Collection Editor of IDE does) }
52  { therefore Notify must be called from here, so count of Items and MenuItems remains same }
53  if assigned(Collection) and assigned(Collection.Owner) and
54    not (csDestroying in (Collection.Owner as TCustomComboBoxEx).ComponentState)
55    and (Collection.Count <= (Collection.Owner as TCustomComboBoxEx).Items.Count)
56    then TComboExItems(Collection).Notify(self, cnDeleting);
57  inherited Destroy;
58end;
59
60{ TComboExItem.Setters }
61
62procedure TComboExItem.SetIndent(AValue: Integer);
63begin
64  if FIndent=AValue then exit;
65  FIndent:=AValue;
66  Changed(False);
67end;
68
69procedure TComboExItem.SetOverlayImageIndex(AValue: TImageIndex);
70begin
71  if FOverlayImageIndex=AValue then exit;
72  FOverlayImageIndex:=AValue;
73  { Changed(False); }
74end;
75
76procedure TComboExItem.SetSelectedImageIndex(AValue: TImageIndex);
77begin
78  if FSelectedImageIndex=AValue then exit;
79  FSelectedImageIndex:=AValue;
80  Changed(False);
81end;
82
83{ TListControlItems }
84
85function TListControlItems.Add: TListControlItem;
86begin
87  Result:=TListControlItem.Create(self);
88end;
89
90function TListControlItems.CompareItems(AItem1, AItem2: TListControlItem): Integer;
91begin
92  if CaseSensitive
93    then Result:=CompareStr((AItem1 as TListControlItem).Caption,
94                            (AItem2 as TListControlItem).Caption)
95    else Result:=CompareStr(lowercase((AItem1 as TListControlItem).Caption),
96                            lowercase((AItem2 as TListControlItem).Caption));
97end;
98
99procedure TListControlItems.CustomSort(ACompare: TListItemsCompare);
100begin
101  if assigned(ACompare) then
102    begin
103      FCompare:=ACompare;
104      Sort;
105      FCompare:=nil;
106    end;
107end;
108
109function TListControlItems.DoCustomSort(AItem1, AItem2: TListControlItem): Integer;
110begin
111  Result:=FCompare(self, AItem1.Index, AItem2.Index);
112end;
113
114function TListControlItems.DoOnCompare(AItem1, AItem2: TListControlItem): Integer;
115begin
116  Result:=OnCompare(self, AItem1, AItem2);
117end;
118
119procedure TListControlItems.Sort;
120var pCompareItems: function(AItem1, AItem2: TListControlItem): Integer of object;
121
122  procedure QuickSort(aTop, aBottom: Integer);
123  var i, j, aPivot: Integer;
124  begin
125    repeat
126      i:=aTop;
127      j:=aBottom;
128      aPivot:=(aTop+aBottom) div 2;
129      repeat
130        while pCompareItems(Items[aPivot], Items[i])>0 do
131          inc(i);
132        while pCompareItems(Items[aPivot], Items[j])<0 do
133          dec(j);
134        if i<=j then
135          begin
136            if i<>j then
137              if pCompareItems(Items[i], Items[j])<>0 then Exchange(i, j);
138            if aPivot=i
139              then aPivot:=j
140              else if aPivot=j then aPivot:=i;
141            inc(i);
142            dec(j);
143          end;
144      until i>j;
145      if aTop<j then QuickSort(aTop, j);
146      aTop:=i;
147    until i>=aBottom;
148  end;
149
150var aID: Integer;
151begin
152  pCompareItems:=nil;
153  if assigned(FCompare)
154    then pCompareItems:=@DoCustomSort
155    else
156    case SortType of
157      stData: if assigned(OnCompare) then pCompareItems:=@DoOnCompare;
158      stText: pCompareItems:=@CompareItems;
159      stBoth: if assigned(OnCompare)
160                then pCompareItems:=@DoOnCompare
161                else pCompareItems:=@CompareItems;
162    end;
163  aID:=Items[(Owner as TCustomComboBoxEx).ItemIndex].ID;
164  BeginUpdate;
165  if assigned(pCompareItems) then QuickSort(0, Count-1);
166  (Owner as TCustomComboBoxEx).ItemIndex:=FindItemID(aID).Index;
167  EndUpdate;
168end;
169
170procedure TListControlItems.Update(AItem: TCollectionItem);
171begin
172  inherited Update(AItem);
173end;
174
175{ TListControlItems.Getters and Setters }
176
177function TListControlItems.GetItems(AIndex: Integer): TListControlItem;
178begin
179  Result:=GetItem(AIndex) as TListControlItem;
180end;
181
182procedure TListControlItems.SetCaseSensitive(AValue: Boolean);
183begin
184  if FCaseSensitive=AValue then exit;
185  FCaseSensitive:=AValue;
186end;
187
188procedure TListControlItems.SetSortType(AValue: TListItemsSortType);
189begin
190  if FSortType=AValue then exit;
191  FSortType:=AValue;
192  Sort;
193end;
194
195{ TComboExItems }
196
197function TComboExItems.Add: TComboExItem;
198begin
199  Result:=TComboExItem.Create(self);
200end;
201
202function TComboExItems.AddItem(const ACaption: string; AImageIndex: SmallInt;
203  AOverlayImageIndex: SmallInt; ASelectedImageIndex: SmallInt; AIndent: SmallInt; AData: TCustomData
204  ): TComboExItem;
205begin
206  Result:=Add();
207  with Result do
208    begin
209      Caption:=ACaption;
210      Indent:=AIndent;
211      ImageIndex:=AImageIndex;
212      OverlayImageIndex:=AOverlayImageIndex;
213      SelectedImageIndex:=ASelectedImageIndex;
214      Data:=AData;
215    end;
216end;
217
218function TComboExItems.Insert(AIndex: Integer): TComboExItem;
219begin
220  Result := TComboExItem(inherited Insert(AIndex));
221end;
222
223procedure TComboExItems.Notify(Item: TCollectionItem; Action: TCollectionNotification);
224var i: Integer;
225begin
226  inherited Notify(Item, Action);
227  case Action of
228    cnAdded:
229      begin
230        FAddingOrDeletingItem:=True;
231        with Owner as TCustomComboBoxEx do
232          begin
233            Items.Add('');
234            if not (csLoading in ComponentState) then
235              TComboExItem(Item).FCaption:=TComboExItem.cDefCaption+inttostr(Item.ID);
236          end;
237      end;
238    cnDeleting:
239      begin
240        FAddingOrDeletingItem:=True;
241        with Owner as TCustomComboBoxEx do
242          begin
243            i:=ItemIndex;
244            Items.Delete(Item.Index);
245            if i<Count then ItemIndex:=i
246              else if i>0 then ItemIndex:=i-1;
247          end;
248      end;
249  end;
250end;
251
252procedure TComboExItems.Update(Item: TCollectionItem);
253var aItemIndex: Integer;
254begin
255  inherited Update(Item);
256  aItemIndex:=(Owner as TCustomComboBoxEx).ItemIndex;
257  if not assigned(Item) or ((aItemIndex>=0) and
258    (Item=(Owner as TCustomComboBoxEx).ItemsEx[aItemIndex]))
259    then (Owner as TCustomComboBoxEx).Invalidate;
260  FAddingOrDeletingItem:=False;
261end;
262
263{ TComboExItems.Getters and Setters }
264
265function TComboExItems.GetComboItems(AIndex: Integer): TComboExItem;
266begin
267  Result:=Items[AIndex] as TComboExItem;
268end;
269
270{ TCustomComboBoxEx }
271
272constructor TCustomComboBoxEx.Create(TheOwner: TComponent);
273begin
274  inherited Create(TheOwner);
275  FAutoCompleteOptions:=cDefAutoCompOpts;
276  FItemsEx:=TComboExItems.Create(self, TComboExItem);
277  FNeedMeasure:=True;
278  inherited Style:=csOwnerDrawFixed;
279  FStyle:=cDefStyle;
280  FStyleEx:=[];
281end;
282
283destructor TCustomComboBoxEx.Destroy;
284begin
285  FreeAndNil(FItemsEx);
286  inherited Destroy;
287end;
288
289procedure TCustomComboBoxEx.Add(const ACaption: string; AIndent: Integer;
290  AImgIdx: TImageIndex; AOverlayImgIdx: TImageIndex; ASelectedImgIdx: TImageIndex);
291begin
292  Insert(ItemsEx.Count, ACaption, AIndent, AImgIdx, AOverlayImgIdx, ASelectedImgIdx);
293end;
294
295function TCustomComboBoxEx.Add: Integer;
296begin
297  Result:=ItemsEx.Count;
298  Insert(Result, TComboExItem.cDefCaption);
299end;
300
301procedure TCustomComboBoxEx.AddItem(const Item: String; AnObject: TObject);
302begin
303  Insert(ItemsEx.Count, Item);
304  ItemsEx[ItemsEx.Count].Data:=AnObject;
305end;
306
307procedure TCustomComboBoxEx.AssignItemsEx(AItemsEx: TComboExItems);
308begin
309  ItemsEx.Assign(AItemsEx);
310end;
311
312procedure TCustomComboBoxEx.AssignItemsEx(AItems: TStrings);
313var i: Integer;
314begin
315  FItemsEx.BeginUpdate;
316  FItemsEx.Clear;
317  for i:=0 to AItems.Count-1 do
318    ItemsEx.AddItem(AItems[i]);
319  FItemsEx.EndUpdate;
320end;
321
322procedure TCustomComboBoxEx.Clear;
323begin
324  FItemsEx.Clear;
325end;
326
327procedure TCustomComboBoxEx.CMBiDiModeChanged(var Message: TLMessage);
328begin
329  inherited CMBiDiModeChanged(Message);
330  FRightToLeft:=IsRightToLeft;
331  Invalidate;
332end;
333
334procedure TCustomComboBoxEx.Delete(AIndex: Integer);
335begin
336  ItemsEx.Delete(AIndex);
337end;
338
339procedure TCustomComboBoxEx.DeleteSelected;
340begin
341  if ItemIndex>=0 then Delete(ItemIndex);
342end;
343
344procedure TCustomComboBoxEx.DrawItem(Index: Integer; ARect: TRect; State: TOwnerDrawState);
345const caThemes: array [Boolean] of TThemedButton = (tbPushButtonDisabled, tbPushButtonNormal);
346      cItemIndent: SmallInt = 2;
347var aDetail: TThemedElementDetails;
348    aDropped: Boolean;
349    aEnabled: Boolean;
350    aFlags: Cardinal;
351    aFocusedEditableMainItemNoDD: Boolean;  { combo has edit-like line edit in csDropDownList (Win) and is closed (not DroppedDown }
352    aImgPoint: TPoint;
353    aIndent: SmallInt;
354    aItemIndex: SmallInt;
355    aMainItem: Boolean;
356    anyRect: TRect;
357    ImagesSize: TSize;
358begin  { do not call inherited ! }
359  aDropped:=DroppedDown;
360  aEnabled:=IsEnabled;
361  aMainItem:= (ARect.Left>0);
362  {$IF DEFINED(LCLWin32) or DEFINED(LCLWin64)}
363  aFocusedEditableMainItemNoDD := (Focused and aMainItem and not aDropped);
364  {$ELSE}
365  aFocusedEditableMainItemNoDD := False;
366  {$ENDIF}
367  if aDropped and not aMainItem or aFocusedEditableMainItemNoDD then
368    begin
369      if not (odSelected in State) then Canvas.Brush.Color:=clWindow;
370      Canvas.Brush.Style:=bsSolid;
371      Canvas.FillRect(ARect);
372    end;
373  aDetail:=ThemeServices.GetElementDetails(caThemes[aEnabled]);
374  if FNeedMeasure then
375    begin
376      FTextHeight:=Canvas.TextHeight('ŠjÁÇ');
377      FNeedMeasure := False;
378    end;
379  if not aMainItem
380    then aIndent:=TComboExItem(ItemsEx[Index]).Indent
381    else aIndent:=-1;
382  if aIndent<0 then aIndent:=0;
383  inc(aIndent, cItemIndent);
384  if assigned(Images) then
385    begin
386      aItemIndex:=-1;
387      ImagesSize := Images.SizeForPPI[ImagesWidth, Font.PixelsPerInch];
388      if (aMainItem or (odSelected in State)) and
389        ((ItemsEx[Index].SelectedImageIndex>=0) and (ItemsEx[Index].SelectedImageIndex<Images.Count))
390        then aItemIndex:=ItemsEx[Index].SelectedImageIndex;
391      if aItemIndex<0 then aItemIndex:=ItemsEx[Index].ImageIndex;
392      if aItemIndex>=0 then
393        begin
394          if not FRightToLeft
395            then aImgPoint.X:=ARect.Left+aIndent
396            else aImgPoint.X:=ARect.Right-aIndent-ImagesSize.cx;
397          aImgPoint.Y:=(ARect.Bottom+ARect.Top-ImagesSize.cy) div 2;
398          ThemeServices.DrawIcon(Canvas, aDetail, aImgPoint, Images, aItemIndex);
399        end;
400      inc(aIndent, ImagesSize.cx+2*cItemIndent);
401    end;
402  Canvas.Brush.Style:=bsClear;
403  if (not (odSelected in State) or not aDropped) and not aFocusedEditableMainItemNoDD
404    then Canvas.Font.Color:=clWindowText
405    else Canvas.Font.Color:=clHighlightText;
406  if aFocusedEditableMainItemNoDD then
407    begin
408      LCLIntf.SetBkColor(Canvas.Handle, ColorToRGB(clBtnFace));
409      LCLIntf.DrawFocusRect(Canvas.Handle, aRect);
410    end;
411  aFlags:=DT_END_ELLIPSIS+DT_VCENTER+DT_SINGLELINE+DT_NOPREFIX;
412  if not FRightToLeft then
413    begin
414      anyRect.Left:=ARect.Left+aIndent;
415      anyRect.Right:=ARect.Right;
416    end else
417    begin
418      anyRect.Right:=ARect.Right-aIndent;
419      anyRect.Left:=ARect.Left;
420      aFlags:=aFlags or DT_RIGHT or DT_RTLREADING;
421    end;
422  anyRect.Top:=(ARect.Top+ARect.Bottom-FTextHeight) div 2;
423  anyRect.Bottom:=anyRect.Top+FTextHeight;
424  DrawText(Canvas.Handle, PChar(ItemsEx[Index].Caption), Length(ItemsEx[Index].Caption), anyRect, aFlags);
425end;
426
427procedure TCustomComboBoxEx.FontChanged(Sender: TObject);
428begin
429  FNeedMeasure:=True;
430  inherited FontChanged(Sender);
431end;
432
433procedure TCustomComboBoxEx.InitializeWnd;
434begin
435  inherited InitializeWnd;
436  FRightToLeft:=IsRightToLeft;
437end;
438
439procedure TCustomComboBoxEx.Insert(AIndex: Integer; const ACaption: string; AIndent: Integer = -1;
440            AImgIdx: TImageIndex = -1; AOverlayImgIdx: TImageIndex = -1; ASelectedImgIdx: TImageIndex = -1);
441var aItem: TCollectionItem;
442begin
443  aItem:=ItemsEx.Insert(AIndex);
444  with aItem as TComboExItem do
445    begin
446      Caption:=ACaption;
447      Indent:=AIndent;
448      ImageIndex:=AImgIdx;
449      OverlayImageIndex:=AOverlayImgIdx;
450      SelectedImageIndex:=ASelectedImgIdx;
451    end;
452end;
453
454procedure TCustomComboBoxEx.SetItemHeight(const AValue: Integer);
455begin
456  inherited SetItemHeight(AValue);
457  FNeedMeasure:=True;
458end;
459
460{ TCustomComboBoxEx.Setters }
461
462procedure TCustomComboBoxEx.SetImages(AValue: TCustomImageList);
463begin
464  if FImages=AValue then exit;
465  FImages:=AValue;
466  Invalidate;
467end;
468
469procedure TCustomComboBoxEx.SetImagesWidth(const aImagesWidth: Integer);
470begin
471  if FImagesWidth = aImagesWidth then Exit;
472  FImagesWidth := aImagesWidth;
473  Invalidate;
474end;
475
476procedure TCustomComboBoxEx.SetStyle(AValue: TComboBoxExStyle);
477begin
478  if FStyle=AValue then exit;
479  FStyle:=AValue;
480end;
481
482procedure TCustomComboBoxEx.SetStyleEx(AValue: TComboBoxExStyles);
483begin
484  if FStyleEx=AValue then exit;
485  FStyleEx:=AValue;
486end;
487
488{ TCustomCheckCombo }
489
490constructor TCustomCheckCombo.Create(AOwner: TComponent);
491begin
492  inherited Create(AOwner);
493  TStringList(Items).Duplicates:=dupIgnore;
494  Style:=csOwnerDrawFixed;
495  FNeedMeasure:=True;
496  FRejectToggleOnSelect:=True;
497end;
498
499destructor TCustomCheckCombo.Destroy;
500begin
501  ClearItemStates;
502  inherited Destroy;
503end;
504
505procedure TCustomCheckCombo.AddItem(const AItem: string; AState: TCheckBoxState; AEnabled: Boolean);
506var pItemState: TCheckComboItemState;
507begin
508  pItemState:=TCheckComboItemState.Create;
509  pItemState.State:=aState;
510  pItemState.Enabled:=AEnabled;
511  pItemState.Data:=nil;
512  inherited AddItem(AItem, pItemState);
513end;
514
515procedure TCustomCheckCombo.AssignItems(AItems: TStrings);
516begin
517  ClearItemStates;
518  Items.Assign(AItems);
519  InitItemStates;
520end;
521
522procedure TCustomCheckCombo.CheckAll(AState: TCheckBoxState; AAllowGrayed: Boolean;
523  AAllowDisabled: Boolean);
524var i: Integer;
525begin
526  for i:=0 to Items.Count-1 do
527  begin
528    if (AAllowGrayed or (State[i]<>cbGrayed)) and (AAllowDisabled or ItemEnabled[i])
529      then State[i]:=AState;
530  end;
531end;
532
533procedure TCustomCheckCombo.Clear;
534begin
535  ClearItemStates;
536  inherited Clear;
537end;
538
539procedure TCustomCheckCombo.ClearItemStates;
540var i: Integer;
541begin
542  for i:=0 to Items.Count-1 do
543  begin
544    Items.Objects[i].Free;
545    Items.Objects[i]:=nil;
546  end;
547end;
548
549procedure TCustomCheckCombo.CloseUp;
550begin
551  FDropped:=False;
552  if FRejectDropDown then
553  begin
554    FRejectDropDown:=False;
555    Update;
556  end else
557    inherited CloseUp;
558end;
559
560procedure TCustomCheckCombo.CMBiDiModeChanged(var Message: TLMessage);
561begin
562  inherited CMBiDiModeChanged(Message);
563  FRightToLeft:=IsRightToLeft;
564  FNeedMeasure:=True;
565  Invalidate;
566end;
567
568procedure TCustomCheckCombo.DeleteItem(AIndex: Integer);
569begin
570  if (AIndex>=0) and (AIndex<Items.Count) then
571  begin
572    Items.Objects[AIndex].Free;
573    Items.Delete(AIndex);
574  end;
575end;
576
577procedure TCustomCheckCombo.DrawItem(Index: Integer; ARect: TRect; State: TOwnerDrawState);
578                            { Enabled, State, Highlighted }
579const caCheckThemes: array [Boolean, TCheckBoxState, Boolean] of TThemedButton =
580                           { normal, highlighted }
581        (((tbCheckBoxUncheckedDisabled, tbCheckBoxUncheckedDisabled),  { disabled, unchecked }
582          (tbCheckBoxCheckedDisabled, tbCheckBoxCheckedDisabled),      { disabled, checked }
583          (tbCheckBoxMixedDisabled, tbCheckBoxMixedDisabled)),         { disabled, greyed }
584         ((tbCheckBoxUncheckedNormal, tbCheckBoxUncheckedHot),         { enabled, unchecked }
585          (tbCheckBoxCheckedNormal, tbCheckBoxCheckedHot),             { enabled, checked }
586          (tbCheckBoxMixedNormal, tbCheckBoxMixedHot)));               { enabled, greyed }
587      cCheckIndent: SmallInt = 2;
588      cTextIndent: SmallInt = 5;
589var aDetail: TThemedElementDetails;
590    aDropped: Boolean;
591    aEnabled: Boolean;
592    aFlags: Cardinal;
593    aFocusedEditableMainItemNoDD: Boolean;  { combo has edit-like line edit in csDropDownList (Win) and is closed (not DroppedDown }
594    aGray: Byte;
595    anyRect: TRect;
596    aState: TCheckBoxState;
597    ItemState: TCheckComboItemState;
598begin  { do not call inherited ! }
599  ItemState:=TCheckComboItemState(Items.Objects[Index]);
600  if not (ItemState is TCheckComboItemState) then
601    QueueCheckItemStates;
602  aDropped:=DroppedDown;
603  if aDropped and FRejectDropDown then
604    begin
605      DroppedDown:=False;
606      exit;  { Exit! }
607    end;
608  aEnabled:=IsEnabled;
609  if not (csDesigning in ComponentState) then
610    aEnabled:= (aEnabled and ItemState.Enabled);
611  {$IF DEFINED(LCLWin32) or DEFINED(LCLWin64)}
612  aFocusedEditableMainItemNoDD := (Focused and (ARect.Left>0) and not aDropped);
613  {$ELSE}
614  aFocusedEditableMainItemNoDD := False;
615  {$ENDIF}
616  if (ARect.Left=0) or aFocusedEditableMainItemNoDD then
617    begin
618      if odSelected in State then
619        begin
620          if not aEnabled then
621            begin
622              aGray:=ColorToGray(Canvas.Brush.Color);
623              Canvas.Brush.Color:=RGBToColor(aGray, aGray, aGray);
624            end;
625        end else
626        Canvas.Brush.Color:=clWindow;
627      Canvas.Brush.Style:=bsSolid;
628      Canvas.FillRect(ARect);
629    end;
630  if not (csDesigning in ComponentState)
631    then aState:=ItemState.State
632    else aState:=cbUnchecked;
633  aDetail:=ThemeServices.GetElementDetails(caCheckThemes
634    [aEnabled, aState, not aDropped and FCheckHighlight]);
635  if FNeedMeasure then
636    begin
637      FCheckSize:=ThemeServices.GetDetailSize(aDetail);
638      FTextHeight:=Canvas.TextHeight('ŠjÁÇ');
639      if not aDropped then
640        begin
641          if not FRightToLeft then
642            begin
643              FHiLiteLeft:=-1;
644              FHiLiteRight:=ARect.Right;
645            end else
646            begin
647              FHiLiteLeft:=ARect.Left;
648              FHiLiteRight:=ARect.Right;
649            end;
650          FNeedMeasure := False;
651        end;
652    end;
653  if not FRightToLeft
654    then anyRect.Left:=ARect.Left+cCheckIndent
655    else anyRect.Left:=ARect.Right-cCheckIndent-FCheckSize.cx;
656  anyRect.Right:=anyRect.Left+FCheckSize.cx;
657  anyRect.Top:=(ARect.Bottom+ARect.Top-FCheckSize.cy) div 2;
658  anyRect.Bottom:=anyRect.Top+FCheckSize.cy;
659  ThemeServices.DrawElement(Canvas.Handle, aDetail, anyRect);
660  Canvas.Brush.Style:=bsClear;
661  if (not (odSelected in State) or not aDropped) and not aFocusedEditableMainItemNoDD
662    then Canvas.Font.Color:=clWindowText
663    else begin
664       Canvas.Font.Color:=clHighlightText;
665       FHilightedIndex:=Index;
666    end;
667  if aFocusedEditableMainItemNoDD then
668    begin
669      LCLIntf.SetBkColor(Canvas.Handle, ColorToRGB(clBtnFace));
670      LCLIntf.DrawFocusRect(Canvas.Handle, aRect);
671    end;
672  aFlags:=DT_END_ELLIPSIS+DT_VCENTER+DT_SINGLELINE+DT_NOPREFIX;
673  if not FRightToLeft then
674    begin
675      anyRect.Left:=ARect.Left+cCheckIndent+FCheckSize.cx+cTextIndent;
676      anyRect.Right:=ARect.Right;
677    end else
678    begin
679      anyRect.Right:=anyRect.Left-cTextIndent;
680      anyRect.Left:=ARect.Left;
681      aFlags:=aFlags or DT_RIGHT or DT_RTLREADING;
682    end;
683  anyRect.Top:=(ARect.Top+ARect.Bottom-FTextHeight) div 2;
684  anyRect.Bottom:=anyRect.Top+FTextHeight;
685  DrawText(Canvas.Handle, PChar(Items[Index]), Length(Items[Index]), anyRect, aFlags);
686end;
687
688procedure TCustomCheckCombo.DropDown;
689{$IF DEFINED(LCLWin32) or DEFINED(LCLWin64)}
690{$ELSE}
691var aCursorPos: TPoint;
692    aRect: TRect;
693{$ENDIF}
694begin
695  {$IF DEFINED(LCLWin32) or DEFINED(LCLWin64)}
696  FRejectDropDown:=False;
697  {$ELSE}
698  aCursorPos:=ScreenToControl(Mouse.CursorPos);
699  aRect:=Rect(FHiLiteLeft, 0, FHiLiteRight, Height);
700  FRejectDropDown:=PtInRect(aRect, aCursorPos);
701  {$ENDIF}
702  FDropped:=True;
703  if not FRejectDropDown then
704    begin
705      inherited DropDown;
706      FRejectToggleOnSelect:=False;
707    end else
708    if (ItemIndex>=0) and ItemEnabled[ItemIndex] then Toggle(ItemIndex);
709end;
710
711procedure TCustomCheckCombo.FontChanged(Sender: TObject);
712begin
713  FNeedMeasure:=True;
714  inherited FontChanged(Sender);
715end;
716
717procedure TCustomCheckCombo.InitializeWnd;
718begin
719  InitItemStates;
720  inherited InitializeWnd;
721  CheckItemStates;
722  FRightToLeft:=IsRightToLeft;
723end;
724
725procedure TCustomCheckCombo.InitItemStates;
726var i: Integer;
727    pItemState: TCheckComboItemState;
728begin
729  for i:=0 to Items.Count-1 do
730    if Items.Objects[i]=nil then begin
731      pItemState:=TCheckComboItemState.Create;
732      pItemState.Enabled:=True;
733      pItemState.State:=cbUnchecked;
734      pItemState.Data:=nil;
735      Items.Objects[i]:=pItemState;
736    end else if not (Items.Objects[i] is TCheckComboItemState) then
737      raise Exception.Create(DbgSName(Self)+': Item '+IntToStr(i)+' is not a TCheckComboItemState');
738end;
739
740procedure TCustomCheckCombo.CheckItemStates;
741var
742  i: Integer;
743begin
744  for i:=0 to Items.Count-1 do
745    if not (Items.Objects[i] is TCheckComboItemState) then
746      raise Exception.Create(DbgSName(Self)+': Item '+IntToStr(i)+' is not a TCheckComboItemState');
747end;
748
749procedure TCustomCheckCombo.QueueCheckItemStates;
750begin
751  Application.QueueAsyncCall(@AsyncCheckItemStates,0);
752end;
753
754procedure TCustomCheckCombo.KeyDown(var Key: Word; Shift: TShiftState);
755begin
756  case Key of
757    VK_RETURN:
758      if FDropped then
759        if (ItemIndex=FHilightedIndex) and ItemEnabled[ItemIndex] then Toggle(ItemIndex);
760    VK_SPACE:
761      if DroppedDown then
762        if (ItemIndex>=0) and ItemEnabled[ItemIndex] then
763        begin
764          if ItemIndex<>FHilightedIndex then
765          begin
766            ItemIndex:=FHilightedIndex;
767            inherited Select;
768          end;
769          Toggle(ItemIndex);
770          DroppedDown:=False;
771        end;
772  end;
773  inherited KeyDown(Key, Shift);
774end;
775
776procedure TCustomCheckCombo.Loaded;
777begin
778  inherited Loaded;
779  InitItemStates;
780end;
781
782procedure TCustomCheckCombo.MouseLeave;
783begin
784  FCheckHighlight:=False;
785  inherited MouseLeave;
786end;
787
788procedure TCustomCheckCombo.MouseMove(Shift: TShiftState; X, Y: Integer);
789var aHighlight: Boolean;
790begin
791  inherited MouseMove(Shift, X, Y);
792  aHighlight:=((X>FHiLiteLeft) and (X<FHiLiteRight));
793  if aHighlight<>FCheckHighlight then
794    begin
795      FCheckHighlight:=aHighlight;
796      Invalidate;
797    end;
798end;
799
800procedure TCustomCheckCombo.Select;
801begin
802  inherited Select;
803  {$IF DEFINED(LCLWin32) or DEFINED(LCLWin64)}
804  if DroppedDown then FRejectToggleOnSelect:=True;
805  {$ENDIF}
806  if not FRejectToggleOnSelect then
807    begin
808      if (ItemIndex >= 0) and ItemEnabled[ItemIndex] then Toggle(ItemIndex);
809      FRejectToggleOnSelect:=True;
810    end;
811  FDropped:=False;
812end;
813
814procedure TCustomCheckCombo.SetItemHeight(const AValue: Integer);
815begin
816  inherited SetItemHeight(AValue);
817  FNeedMeasure:=True;
818end;
819
820procedure TCustomCheckCombo.SetItems(const Value: TStrings);
821begin
822  ClearItemStates;
823  inherited SetItems(Value);
824  InitItemStates;
825end;
826
827procedure TCustomCheckCombo.Toggle(AIndex: Integer);
828const caNewStateMap: array [TCheckBoxState, Boolean] of TCheckBoxState =
829  { False (AllowGrayed) True }
830  ((cbChecked, cbGrayed),       { cbUnchecked }
831   (cbUnChecked, cbUnChecked),  { cbChecked }
832   (cbChecked, cbChecked));     { cbGrayed }
833begin
834  State[AIndex]:=caNewStateMap[State[AIndex], AllowGrayed];
835end;
836
837{ TCustomCheckCombo.Getters and Setters }
838
839function TCustomCheckCombo.GetChecked(AIndex: Integer): Boolean;
840begin
841  Result:=(TCheckComboItemState(Items.Objects[AIndex]).State=cbChecked);
842end;
843
844procedure TCustomCheckCombo.AsyncCheckItemStates(Data: PtrInt);
845begin
846  CheckItemStates;
847end;
848
849function TCustomCheckCombo.GetCount: Integer;
850begin
851  Result:=Items.Count;
852end;
853
854function TCustomCheckCombo.GetItemEnabled(AIndex: Integer): Boolean;
855begin
856  Result:=TCheckComboItemState(Items.Objects[AIndex]).Enabled;
857end;
858
859function TCustomCheckCombo.GetObject(AIndex: Integer): TObject;
860begin
861  Result:=TCheckComboItemState(Items.Objects[AIndex]).Data;
862end;
863
864function TCustomCheckCombo.GetState(AIndex: Integer): TCheckBoxState;
865begin
866  Result:=TCheckComboItemState(Items.Objects[AIndex]).State;
867end;
868
869procedure TCustomCheckCombo.SetChecked(AIndex: Integer; AValue: Boolean);
870begin
871  if AValue=(TCheckComboItemState(Items.Objects[AIndex]).State=cbChecked) then exit;
872  if AValue
873    then TCheckComboItemState(Items.Objects[AIndex]).State:=cbChecked
874    else TCheckComboItemState(Items.Objects[AIndex]).State:=cbUnchecked;
875  if Assigned(FOnItemChange) then
876    FOnItemChange(Self, AIndex);
877  if AIndex=ItemIndex then
878    Invalidate;
879end;
880
881procedure TCustomCheckCombo.SetItemEnabled(AIndex: Integer; AValue: Boolean);
882begin
883  if TCheckComboItemState(Items.Objects[AIndex]).Enabled=AValue then exit;
884  TCheckComboItemState(Items.Objects[AIndex]).Enabled:=AValue;
885  if AIndex=ItemIndex then
886    Invalidate;
887end;
888
889procedure TCustomCheckCombo.SetObject(AIndex: Integer; AValue: TObject);
890begin
891  TCheckComboItemState(Items.Objects[AIndex]).Data:=AValue;
892end;
893
894procedure TCustomCheckCombo.SetState(AIndex: Integer; AValue: TCheckBoxState);
895begin
896  if TCheckComboItemState(Items.Objects[AIndex]).State=AValue then exit;
897  TCheckComboItemState(Items.Objects[AIndex]).State:=AValue;
898  if Assigned(FOnItemChange) then
899    FOnItemChange(self, AIndex);
900  if AIndex=ItemIndex then
901    Invalidate;
902end;
903
904
905