1{%MainUnit ../buttons.pp}
2
3{******************************************************************************
4                                   TCustomSpeedButton
5 ******************************************************************************
6
7 *****************************************************************************
8  This file is part of the Lazarus Component Library (LCL)
9
10  See the file COPYING.modifiedLGPL.txt, included in this distribution,
11  for details about the license.
12 *****************************************************************************
13}
14
15{$IFOPT C-}
16// Uncomment for local trace
17//  {$C+}
18//  {$DEFINE ASSERT_IS_ON}
19{$ENDIF}
20
21const
22  UpState: array[Boolean] of TButtonState =
23  (
24{False} bsUp, // mouse in control = false
25{True } bsHot // mouse in contorl = true
26  );
27
28{------------------------------------------------------------------------------
29  Method:  TCustomSpeedButton.Create
30  Params:  none
31  Returns: Nothing
32
33  Constructor for the class.
34 ------------------------------------------------------------------------------}
35constructor TCustomSpeedButton.Create(AOwner: TComponent);
36begin
37  inherited Create(AOwner);
38  FGlyph := TButtonGlyph.Create;
39  FGlyph.IsDesigning := csDesigning in ComponentState;
40  FGlyph.ShowMode := gsmAlways;
41  FGlyph.SetTransparentMode(gtmTransparent);
42  FGlyph.OnChange := @GlyphChanged;
43  FImageChangeLink := TChangeLink.Create;
44  FImageChangeLink.OnChange := @ImageListChange;
45
46  with GetControlClassDefaultSize do
47    SetInitialBounds(0, 0, CX, CY);
48  ControlStyle := ControlStyle + [csCaptureMouse]-[csSetCaption, csClickEvents, csOpaque];
49
50  FLayout := blGlyphLeft;
51  FAllowAllUp := False;
52  FMouseInControl := False;
53  FDragging := False;
54  FShowAccelChar := True;
55  FSpacing := 4;
56  FMargin := -1;
57  Color := clBtnFace;
58  FShowCaption := true;
59end;
60
61{------------------------------------------------------------------------------
62  Method: TCustomSpeedButton.Destroy
63  Params:  None
64  Returns: Nothing
65
66  Destructor for the class.
67 ------------------------------------------------------------------------------}
68destructor TCustomSpeedButton.Destroy;
69begin
70  FreeAndNil(FGlyph);
71  FreeAndNil(FImageChangeLink);
72  inherited Destroy;
73end;
74
75{------------------------------------------------------------------------------
76  Method: TCustomSpeedButton.FindDownButton: TCustomSpeedButton;
77
78  Searches the speed button with Down=true and the same GroupIndex.
79 ------------------------------------------------------------------------------}
80function TCustomSpeedButton.FindDownButton: TCustomSpeedButton;
81
82  function FindDown(AWinControl: TWinControl): TCustomSpeedButton;
83  var
84    i: Integer;
85    Child: TControl;
86    Button: TCustomSpeedButton;
87  begin
88    if AWinControl = nil then Exit(nil);
89    for i := 0 to AWinControl.ControlCount-1 do
90    begin
91      Child := AWinControl.Controls[i];
92      if Child is TCustomSpeedButton then
93      begin
94        Button := TCustomSpeedButton(Child);
95        if (Button.GroupIndex=GroupIndex) and (Button.Down) then
96          Exit(Button);
97      end;
98      if Child is TWinControl then
99      begin
100        Result := FindDown(TWinControl(Child));
101        if Result <> nil then Exit;
102      end;
103    end;
104    Result := nil;
105  end;
106
107begin
108  if Down or (GroupIndex=0) then exit(Self);
109  Result := FindDown(GetFirstParentForm(Self));
110end;
111
112procedure TCustomSpeedButton.Click;
113begin
114  inherited Click;
115end;
116
117{------------------------------------------------------------------------------
118  Method: TCustomSpeedButton.SetAllowAllUp
119  Params: Value:
120  Returns:  nothing
121
122 ------------------------------------------------------------------------------}
123procedure TCustomSpeedButton.SetAllowAllUp(Value : Boolean);
124begin
125  if FAllowAllUp <> Value then
126  begin
127    FAllowAllUp := Value;
128    UpdateExclusive;
129  end;
130end;
131
132{------------------------------------------------------------------------------
133  Method: TCustomSpeedButton.SetDown
134  Params: Value:
135  Returns:  nothing
136
137 ------------------------------------------------------------------------------}
138procedure TCustomSpeedButton.SetDown(Value : Boolean);
139var
140  OldState: TButtonState;
141  OldDown: Boolean;
142begin
143  //since Down needs GroupIndex, then we need to wait that all properties
144  //loaded before we continue
145  if (csLoading in ComponentState) then
146  begin
147    FDownLoaded := Value;
148    exit;
149  end else
150  begin
151    if FGroupIndex = 0 then
152      Value:= false;
153    if FDown <> Value then
154    begin
155      if FDown and not FAllowAllUp then
156        Exit;
157      OldDown := FDown;
158      FDown := Value;
159      OldState := FState;
160      if FDown then
161        FState := bsExclusive
162      else
163        FState := UpState[FMouseInControl];
164      if (OldDown <> FDown) or (OldState <> FState) then
165        Invalidate;
166      if Value then
167        UpdateExclusive;
168    end;
169  end;
170end;
171
172{------------------------------------------------------------------------------
173  Method: TCustomSpeedButton.SetFlat
174  Params: Value:
175  Returns:  nothing
176
177 ------------------------------------------------------------------------------}
178procedure TCustomSpeedButton.SetFlat(const Value: Boolean);
179begin
180  if FFlat <> Value then
181  begin
182    FFlat := Value;
183    Invalidate;
184  end;
185end;
186
187{------------------------------------------------------------------------------
188  Method: TCustomSpeedButton.SetGlyph
189  Params: Value:
190  Returns:  nothing
191
192 ------------------------------------------------------------------------------}
193procedure TCustomSpeedButton.SetGlyph(Value : TBitmap);
194begin
195  FGlyph.Glyph := Value;
196  Invalidate;
197end;
198
199{------------------------------------------------------------------------------
200  Method: TCustomSpeedButton.SetGroupIndex
201  Params: Value:
202  Returns:  nothing
203
204 ------------------------------------------------------------------------------}
205procedure TCustomSpeedButton.SetGroupIndex(const Value : Integer);
206begin
207  if FGroupIndex <> Value then
208  begin
209    FGroupIndex := Value;
210    UpdateExclusive;
211  end;
212end;
213
214procedure TCustomSpeedButton.SetImageIndex(const aImageIndex: TImageIndex);
215begin
216  FGlyph.ExternalImageIndex := aImageIndex;
217end;
218
219procedure TCustomSpeedButton.SetImages(const aImages: TCustomImageList);
220begin
221  if FGlyph.ExternalImages <> nil then
222  begin
223    FGlyph.ExternalImages.UnRegisterChanges(FImageChangeLink);
224    FGlyph.ExternalImages.RemoveFreeNotification(Self);
225  end;
226  FGlyph.ExternalImages := aImages;
227  if FGlyph.ExternalImages <> nil then
228  begin
229    FGlyph.ExternalImages.FreeNotification(Self);
230    FGlyph.ExternalImages.RegisterChanges(FImageChangeLink);
231  end;
232  InvalidatePreferredSize;
233  AdjustSize;
234end;
235
236procedure TCustomSpeedButton.SetImageWidth(const aImageWidth: Integer);
237begin
238  FGlyph.ExternalImageWidth := aImageWidth;
239  InvalidatePreferredSize;
240  AdjustSize;
241end;
242
243{------------------------------------------------------------------------------
244  Method: TCustomSpeedButton.SetMargin
245  Params: Value:
246  Returns:  nothing
247
248 ------------------------------------------------------------------------------}
249procedure TCustomSpeedButton.SetMargin(const Value: integer);
250begin
251  if FMargin <> Value then
252  begin
253    FMargin := Value;
254    Invalidate;
255  end;
256end;
257
258{------------------------------------------------------------------------------
259  Method: TCustomSpeedButton.SetNumGlyphs
260  Params: Value : Integer = Number of glyphs in the file/resource
261  Returns:  nothing
262
263 ------------------------------------------------------------------------------}
264procedure TCustomSpeedButton.SetNumGlyphs(Value : integer);
265begin
266  if Value < Low(TNumGlyphs) then Value := Low(TNumGlyphs);
267  if Value > High(TNumGlyphs) then Value := High(TNumGlyphs);
268
269  if Value <> TButtonGlyph(fGlyph).NumGlyphs then
270  begin
271    TButtonGlyph(fGlyph).NumGlyphs := TNumGlyphs(Value);
272    Invalidate;
273  end;
274end;
275
276{------------------------------------------------------------------------------
277  Method: TCustomSpeedButton.SetSpacing
278  Params: Value:
279  Returns:  nothing
280
281 ------------------------------------------------------------------------------}
282procedure TCustomSpeedButton.SetSpacing(const Value: integer);
283begin
284  if FSpacing <> Value then
285  begin
286    FSpacing := Value;
287    Invalidate;
288  end;
289end;
290
291procedure TCustomSpeedButton.SetShowAccelChar(Value: boolean);
292begin
293  If FShowAccelChar <> Value then
294  begin
295    FShowAccelChar := Value;
296    Invalidate;
297  end;
298end;
299
300
301{------------------------------------------------------------------------------
302  procedure TCustomSpeedButton.RealSetText(const Value: TCaption);
303 ------------------------------------------------------------------------------}
304procedure TCustomSpeedButton.RealSetText(const Value: TCaption);
305begin
306  if Caption = Value then Exit;
307  if (Parent<>nil) and (Parent.HandleAllocated) and (not (csLoading in ComponentState)) then
308  begin
309    InvalidatePreferredSize;
310    inherited RealSetText(Value);
311    AdjustSize;
312  end else
313    inherited RealSetText(Value);
314
315  Invalidate;
316end;
317
318{------------------------------------------------------------------------------
319  procedure TCustomSpeedButton.UpdateState(InvalidateOnChange: boolean);
320 ------------------------------------------------------------------------------}
321procedure TCustomSpeedButton.UpdateState(InvalidateOnChange: boolean);
322var
323  OldState: TButtonState;
324begin
325  OldState := FState;
326  if not IsEnabled then
327  begin
328    FState := bsDisabled;
329    FDragging := False;
330  end else
331  begin
332    if FState = bsDisabled then
333    begin
334      if FDown and (GroupIndex <> 0) then
335        FState := bsExclusive
336      else
337        FState := UpState[FMouseInControl];
338    end
339    else
340    if (FState in [bsHot, bsDown]) and (not FMouseInControl) and (not FDragging) and (not FDown) then
341    begin
342      // return to normal
343      FState := bsUp;
344    end
345    else
346    if (FState = bsUp) and FMouseInControl then
347      FState := bsHot;
348  end;
349  if FState <> OldState then
350    if (Action is TCustomAction) then
351      TCustomAction(Action).Checked := FState = bsDown;
352  //if InvalidateOnChange then DebugLn(['TCustomSpeedButton.UpdateState ',DbgSName(Self),' InvalidateOnChange=',InvalidateOnChange,' StateChange=',FState<>OldState]);
353  if InvalidateOnChange and
354     (
355       (FState <> OldState) or
356       not ThemedElementDetailsEqual(FLastDrawDetails, GetDrawDetails)
357     )
358  then
359    Invalidate;
360end;
361
362{------------------------------------------------------------------------------
363  function TCustomSpeedButton.GetDrawDetails: TThemedElementDetails;
364 ------------------------------------------------------------------------------}
365function TCustomSpeedButton.GetDrawDetails: TThemedElementDetails;
366
367  function ButtonPart: TThemedButton;
368  begin
369    // tbPushButtonNormal, tbPushButtonHot, tbPushButtonPressed,
370    // tbPushButtonDisabled, tbPushButtonDefaulted
371
372    // no check states available
373    Result := tbPushButtonNormal;
374    if not IsEnabled then
375      Result := tbPushButtonDisabled
376    else
377    if FState in [bsDown, bsExclusive] then
378      Result := tbPushButtonPressed
379    else
380    if FState = bsHot then
381      Result := tbPushButtonHot
382    else
383      Result := tbPushButtonNormal;
384  end;
385
386  function ToolButtonPart: TThemedToolBar;
387  begin
388    // ttbButtonNormal, ttbButtonHot, ttbButtonPressed, ttbButtonDisabled
389    // ttbButtonChecked, ttbButtonCheckedHot
390    if not IsEnabled then
391      Result := ttbButtonDisabled
392    else
393    begin
394      if Down then
395      begin // checked states
396        if FMouseInControl then
397          Result := ttbButtonCheckedHot
398        else
399          Result := ttbButtonChecked;
400      end
401      else
402      begin
403        if FState in [bsDown, bsExclusive] then
404          Result := ttbButtonPressed else
405        if FState = bsHot then
406          Result := ttbButtonHot
407        else
408          Result := ttbButtonNormal;
409      end;
410    end;
411  end;
412
413begin
414  if Flat then
415    Result := ThemeServices.GetElementDetails(ToolButtonPart)
416  else
417    Result := ThemeServices.GetElementDetails(ButtonPart)
418end;
419
420procedure TCustomSpeedButton.ActionChange(Sender: TObject; CheckDefaults: Boolean);
421var
422  NewAct: TCustomAction;
423  Imgs: TCustomImageList;
424  ImgRes: TScaledImageListResolution;
425begin
426  inherited ActionChange(Sender,CheckDefaults);
427  if Sender is TCustomAction then
428  begin
429    NewAct := TCustomAction(Sender);
430    if (not CheckDefaults) or (GroupIndex = 0) then
431      GroupIndex := NewAct.GroupIndex;
432    if (NewAct.ActionList = nil) or (NewAct.ImageIndex < 0) then Exit;
433    Imgs := NewAct.ActionList.Images;
434    if (Imgs = nil) or (NewAct.ImageIndex >= Imgs.Count) then Exit;
435    ImgRes := Imgs.ResolutionForPPI[ImageWidth,Font.PixelsPerInch,GetCanvasScaleFactor];
436    ImgRes.GetBitmap(NewAct.ImageIndex, Glyph);
437  end;
438end;
439
440function TCustomSpeedButton.ButtonGlyph: TButtonGlyph;
441begin
442  Result := FGlyph;
443end;
444
445function TCustomSpeedButton.GetActionLinkClass: TControlActionLinkClass;
446begin
447  Result := TSpeedButtonActionLink;
448end;
449
450class function TCustomSpeedButton.GetControlClassDefaultSize: TSize;
451begin
452  Result.CX := 23;
453  Result.CY := 22;
454end;
455
456{------------------------------------------------------------------------------
457  Method: TCustomSpeedButton.UpdateExclusive
458  Params: none
459  Returns:  nothing
460
461 ------------------------------------------------------------------------------}
462procedure TCustomSpeedButton.UpdateExclusive;
463var
464  msg : TLMessage;
465begin
466  if (FGroupIndex <> 0) and (Parent <> nil) and (not (csLoading in ComponentState)) then
467  begin
468    Msg.Msg := CM_ButtonPressed;
469    Msg.WParam := FGroupIndex;
470    Msg.LParam := PtrInt(Self);
471    Msg.Result := 0;
472    Parent.Broadcast(Msg);
473  end;
474end;
475
476{------------------------------------------------------------------------------
477  Function: TCustomSpeedButton.GetGlyph
478  Params: none
479  Returns:  The bitmap
480
481 ------------------------------------------------------------------------------}
482function TCustomSpeedButton.GetGlyph : TBitmap;
483begin
484  Result := FGlyph.Glyph;
485end;
486
487function TCustomSpeedButton.IsGlyphStored: Boolean;
488var
489  act: TCustomAction;
490begin
491  result := true;
492  if Action <> nil then
493  begin
494    act := TCustomAction(Action);
495    if (act.ActionList <> nil) and (act.ActionList.Images <> nil) and
496    (act.ImageIndex >= 0) and (act.ImageIndex < act.ActionList.Images.Count) then
497      result := false;
498  end;
499end;
500
501procedure TCustomSpeedButton.SetShowCaption(const AValue: boolean);
502begin
503  if FShowCaption=AValue then exit;
504  FShowCaption:=AValue;
505  invalidate;
506end;
507
508{------------------------------------------------------------------------------
509  Method: TCustomSpeedButton.GetNumGlyphs
510  Params: none
511  Returns:  The number stored in TButtonGlyph(FGlyph).NumGlyphs
512
513 ------------------------------------------------------------------------------}
514function TCustomSpeedButton.GetNumGlyphs : Integer;
515Begin
516  Result :=  TButtonGlyph(fGlyph).NumGlyphs;
517end;
518
519{------------------------------------------------------------------------------
520  Method: TCustomSpeedButton.GlyphChanged
521  Params: Sender - The glyph that changed
522  Returns:  zippo
523
524 ------------------------------------------------------------------------------}
525procedure TCustomSpeedButton.GlyphChanged(Sender : TObject);
526Begin
527  //redraw the button;
528  Invalidate;
529end;
530
531procedure TCustomSpeedButton.ImageListChange(Sender: TObject);
532begin
533  if Sender = Images then Invalidate;
534end;
535
536function TCustomSpeedButton.DialogChar(var Message: TLMKey): boolean;
537begin
538  Result := False;
539  // Sometimes LM_CHAR is received instead of LM_SYSCHAR, maybe intentionally
540  // (LCL handles it) or maybe sent by mistake. In either case exit.
541  if (Message.Msg <> LM_SYSCHAR) or not FShowAccelChar then Exit;
542  if Enabled and IsAccel(Message.CharCode, Caption) then
543  begin
544    Result := True;
545    if GroupIndex <> 0 then
546      SetDown(not FDown);
547    Click;
548  end else
549    Result := inherited DialogChar(Message);
550end;
551
552procedure TCustomSpeedButton.CalculatePreferredSize(var PreferredWidth,
553  PreferredHeight: integer; WithThemeSpace: Boolean);
554var
555  r: TRect;
556begin
557  r:=Rect(0,0,0,0);
558  MeasureDraw(false,r,PreferredWidth,PreferredHeight);
559end;
560
561procedure TCustomSpeedButton.MeasureDraw(Draw: boolean;
562  PaintRect: TRect; out PreferredWidth, PreferredHeight: integer);
563var
564  GlyphWidth, GlyphHeight: Integer;
565  Offset, OffsetCap: TPoint;
566  ClientSize, TotalSize, TextSize, GlyphSize: TSize;
567  M, S : integer;
568  SIndex : Longint;
569  TMP : String;
570  TextFlags: Integer;
571  DrawDetails: TThemedElementDetails;
572  FixedWidth: Boolean;
573  FixedHeight: Boolean;
574  TextRect: TRect;
575  HasGlyph: Boolean;
576  HasText: Boolean;
577  CurLayout: TButtonLayout;
578  SysFont: TFont;
579begin
580  if FGlyph = nil then exit;
581
582  DrawDetails := GetDrawDetails;
583
584  PreferredWidth:=0;
585  PreferredHeight:=0;
586
587  if Draw then begin
588    FLastDrawDetails:=DrawDetails;
589    PaintBackground(PaintRect);
590    FixedWidth:=true;
591    FixedHeight:=true;
592  end else begin
593    FixedWidth:=WidthIsAnchored;
594    FixedHeight:=HeightIsAnchored;
595  end;
596  ClientSize.cx:= PaintRect.Right - PaintRect.Left;
597  ClientSize.cy:= PaintRect.Bottom - PaintRect.Top;
598  //debugln(['TCustomSpeedButton.MeasureDraw Step1 ',DbgSName(Self),' PaintRect=',dbgs(PaintRect)]);
599  // compute size of glyph
600  GlyphSize := GetGlyphSize(Draw,PaintRect);
601  GlyphWidth := GlyphSize.CX;
602  GlyphHeight := GlyphSize.CY;
603  HasGlyph:=(GlyphWidth<>0) and (GlyphHeight<>0);
604  //debugln(['TCustomSpeedButton.MeasureDraw Step2 ',DbgSName(Self),' PaintRect=',dbgs(PaintRect),' GlyphSize=',GlyphWidth,'x',GlyphHeight]);
605
606  // compute size of text
607  CurLayout:=BidiAdjustButtonLayout(UseRightToLeftReading, Layout);
608  if ShowCaption and (Caption<>'') then begin
609    TextRect:=PaintRect;
610    // for wordbreak compute the maximum size for the text
611    if Margin>0 then
612      InflateRect(TextRect,-Margin,-Margin);
613    if HasGlyph then
614    begin
615      if (Spacing>=0) then
616        if CurLayout in [blGlyphLeft,blGlyphRight] then
617          dec(TextRect.Right,Spacing)
618        else
619          dec(TextRect.Bottom,Spacing);
620      if CurLayout in [blGlyphLeft,blGlyphRight] then
621        dec(TextRect.Right,GlyphWidth)
622      else
623        dec(TextRect.Bottom,GlyphHeight);
624    end;
625    if not FixedWidth then
626    begin
627      TextRect.Left:=0;
628      TextRect.Right:=High(TextRect.Right) div 2;
629    end;
630    if not FixedHeight then
631    begin
632      TextRect.Top:=0;
633      TextRect.Bottom:=High(TextRect.Bottom) div 2;
634    end;
635    TextSize := GetTextSize(Draw,TextRect);
636  end else begin
637    TextSize.cx:=0;
638    TextSize.cy:=0;
639  end;
640  HasText:=(TextSize.cx <> 0) or (TextSize.cy <> 0);
641
642  if Caption <> '' then
643  begin
644    TMP := Caption;
645    SIndex := DeleteAmpersands(TMP);
646    If SIndex > 0 then
647      If SIndex <= Length(TMP) then begin
648        FShortcut := Ord(TMP[SIndex]);
649      end;
650  end;
651
652  if HasGlyph and HasText then
653    S:= Spacing
654  else
655    S:= 0;
656  M:=Margin;
657  if not Draw then
658  begin
659    if M<0 then M:=2;
660    if S<0 then S:=M;
661  end;
662
663  // Calculate caption and glyph layout
664  if M = -1 then begin
665    // auto compute margin to center content
666    if S = -1 then begin
667      // use the same value for Spacing and Margin
668      TotalSize.cx:= TextSize.cx + GlyphWidth;
669      TotalSize.cy:= TextSize.cy + GlyphHeight;
670      if Layout in [blGlyphLeft, blGlyphRight] then
671        M:= (ClientSize.cx - TotalSize.cx) div 3
672      else
673        M:= (ClientSize.cy - TotalSize.cy) div 3;
674      S:= M;
675    end else begin
676      // fixed Spacing and center content
677      TotalSize.cx:= GlyphWidth + S + TextSize.cx;
678      TotalSize.cy:= GlyphHeight + S + TextSize.cy;
679      if Layout in [blGlyphLeft, blGlyphRight] then
680        M:= (ClientSize.cx - TotalSize.cx) div 2
681      else
682        M:= (ClientSize.cy - TotalSize.cy) div 2;
683    end;
684  end else begin
685    // fixed Margin
686    if S = -1 then begin
687      // use the rest for Spacing between Glyph and Caption
688      TotalSize.cx:= ClientSize.cx - (Margin + GlyphWidth);
689      TotalSize.cy:= ClientSize.cy - (Margin + GlyphHeight);
690      if Layout in [blGlyphLeft, blGlyphRight] then
691        S:= (TotalSize.cx - TextSize.cx) div 2
692      else
693        S:= (TotalSize.cy - TextSize.cy) div 2;
694    end;
695  end;
696
697  //debugln(['TCustomSpeedButton.MeasureDraw Step3 ',DbgSName(Self),' PaintRect=',dbgs(PaintRect),' GlyphSize=',GlyphWidth,'x',GlyphHeight,' TextSize=',TextSize.cx,'x',TextSize.cy,' S=',S,' M=',M]);
698
699  if Draw then
700  begin
701    case CurLayout of
702      blGlyphLeft : begin
703        Offset.X:= M;
704        Offset.Y:= (ClientSize.cy - GlyphHeight) div 2;
705        OffsetCap.X:= Offset.X + GlyphWidth + S;
706        OffsetCap.Y:= (ClientSize.cy - TextSize.cy) div 2;
707      end;
708      blGlyphRight : begin
709        Offset.X:= ClientSize.cx - M - GlyphWidth;
710        Offset.Y:= (ClientSize.cy - GlyphHeight) div 2;
711        OffsetCap.X:= Offset.X - S - TextSize.cx;
712        OffsetCap.Y:= (ClientSize.cy - TextSize.cy) div 2;
713      end;
714      blGlyphTop : begin
715        Offset.X:= (ClientSize.cx - GlyphWidth) div 2;
716        Offset.Y:= M;
717        OffsetCap.X:= (ClientSize.cx - TextSize.cx) div 2;
718        OffsetCap.Y:= Offset.Y + GlyphHeight + S;
719      end;
720      blGlyphBottom : begin
721        Offset.X:= (ClientSize.cx - GlyphWidth) div 2;
722        Offset.Y:= ClientSize.cy - M - GlyphHeight;
723        OffsetCap.X:= (ClientSize.cx - TextSize.cx) div 2;
724        OffsetCap.Y:= Offset.Y - S - TextSize.cy;
725      end;
726    end;
727
728    DrawGlyph(Canvas, PaintRect, Offset, FState, Transparent, 0);
729
730    if FShowCaption and (Caption <> '') then
731    begin
732      with PaintRect, OffsetCap do
733      begin
734        Left := Left + X;
735        Top := Top + Y;
736      end;
737
738      TextFlags := DT_LEFT or DT_TOP;
739      if UseRightToLeftReading then
740        TextFlags := TextFlags or DT_RTLREADING;
741
742      if Draw then
743      begin
744        SysFont := Screen.SystemFont;
745        if  (SysFont.Color=Font.Color)
746        and ((SysFont.Name=Font.Name) or IsFontNameDefault(Font.Name))
747        and (SysFont.Pitch=Font.Pitch)
748        and (SysFont.Style=Font.Style) then
749          ThemeServices.DrawText(Canvas, DrawDetails, Caption, PaintRect, TextFlags, 0)
750        else
751        begin
752          Canvas.Brush.Style := bsClear;
753          DrawText(Canvas.Handle, PChar(Caption), Length(Caption), PaintRect, TextFlags);
754        end;
755      end;
756    end;
757  end else begin
758    // measuring, not drawing
759    case CurLayout of
760      blGlyphLeft, blGlyphRight :
761        begin
762          PreferredWidth:=2*M+S+GlyphWidth+TextSize.cx;
763          PreferredHeight:=2*M+Max(GlyphHeight,TextSize.cy);
764        end;
765      blGlyphTop, blGlyphBottom :
766        begin
767          PreferredWidth:=2*M+Max(GlyphWidth,TextSize.cx);
768          PreferredHeight:=2*M+S+GlyphHeight+TextSize.cy;
769        end;
770    end;
771  end;
772end;
773
774{------------------------------------------------------------------------------
775  Method: TCustomSpeedButton.Paint
776  Params: none
777  Returns:  nothing
778
779 ------------------------------------------------------------------------------}
780procedure TCustomSpeedButton.Paint;
781var
782  PaintRect: TRect;
783  PreferredWidth: integer;
784  PreferredHeight: integer;
785begin
786  UpdateState(false);
787  if FGlyph = nil then exit;
788
789  PaintRect:=ClientRect;
790  MeasureDraw(true,PaintRect,PreferredWidth,PreferredHeight);
791
792  inherited Paint;
793end;
794
795procedure TCustomSpeedButton.PaintBackground(var PaintRect: TRect);
796begin
797  if not Transparent and ThemeServices.HasTransparentParts(FLastDrawDetails) then
798  begin
799    Canvas.Brush.Color := Color;
800    Canvas.FillRect(PaintRect);
801  end;
802  ThemeServices.DrawElement(Canvas.Handle, FLastDrawDetails, PaintRect);
803  PaintRect := ThemeServices.ContentRect(Canvas.Handle, FLastDrawDetails, PaintRect);
804end;
805
806{------------------------------------------------------------------------------
807  Method: TCustomSpeedButton.MouseDown
808  Params: Button:
809          Shift:
810          X, Y:
811  Returns:  nothing
812 ------------------------------------------------------------------------------}
813procedure TCustomSpeedButton.MouseDown(Button: TMouseButton; Shift: TShiftState;
814  X, Y: Integer);
815begin
816  inherited MouseDown(Button, Shift, X, Y);
817  if csDesigning in ComponentState then exit;
818
819  if (Button = mbLeft) and IsEnabled then
820  begin
821    if not FDown then
822    begin
823      FState := bsDown;
824      if (Action is TCustomAction) then
825        TCustomAction(Action).Checked := False;
826      Invalidate;
827    end;
828    FDragging := True;
829  end;
830end;
831
832{------------------------------------------------------------------------------
833  Method: TCustomSpeedButton.MouseMove
834  Params: Shift:
835          X, Y:
836  Returns:  nothing
837
838 ------------------------------------------------------------------------------}
839procedure TCustomSpeedButton.MouseMove(Shift: TShiftState; X, Y: Integer);
840var
841  NewState: TButtonState;
842begin
843  inherited MouseMove(Shift, X, Y);
844  if csDesigning in ComponentState then exit;
845
846  if FDragging then
847  begin
848    //DebugLn('Trace:FDragging is true');
849    if FDown then
850      NewState := bsExclusive
851    else
852    begin
853      if  (X >= 0) and (X < Width) and (Y >= 0) and (Y < Height) then
854        NewState := bsDown
855      else
856        NewState := UpState[FMouseInControl];
857    end;
858
859    if NewState <> FState then
860    begin
861      //debugln(['TCustomSpeedButton.MouseMove ',DbgSName(Self),' fState=',ord(fstate),' NewState=',ord(NewState)]);
862      FState := NewState;
863      Invalidate;
864    end;
865  end;
866end;
867
868{------------------------------------------------------------------------------
869  Method: TCustomSpeedButton.MouseUp
870  Params: Button:
871          Shift:
872          X, Y:
873  Returns:  nothing
874 ------------------------------------------------------------------------------}
875procedure TCustomSpeedButton.MouseUp(Button: TMouseButton; Shift: TShiftState;
876  X, Y: Integer);
877begin
878  inherited MouseUp(Button, Shift, X, Y);
879end;
880
881procedure TCustomSpeedButton.Notification(AComponent: TComponent;
882  Operation: TOperation);
883begin
884  inherited Notification(AComponent, Operation);
885  if (Operation = opRemove) and (FGlyph<>nil) and (AComponent = FGlyph.ExternalImages) then
886    Images := nil;
887end;
888
889{------------------------------------------------------------------------------
890       TCustomSpeedButton DoMouseUp  "Event Handler"
891------------------------------------------------------------------------------}
892procedure TCustomSpeedButton.DoMouseUp(var Message: TLMMouse; Button: TMouseButton);
893begin
894  if not (csNoStdEvents in ControlStyle) then
895    with Message do
896      MouseUp(Button, KeysToShiftState(Keys), XPos, YPos);
897end;
898
899procedure TCustomSpeedButton.WMLButtonDown(var Message: TLMLButtonDown);
900begin
901  inherited;
902
903  // because csClickEvents is not set no csClicked is set in the inherited method
904  Include(FControlState, csClicked);
905end;
906
907procedure TCustomSpeedButton.WMLButtonDBLCLK(var Message: TLMLButtonDblClk);
908begin
909  inherited;
910  // if in a group, raise dblclick event, otherwise translate to click event
911  if Down then
912    DblClick
913  else
914    Click;
915end;
916
917class procedure TCustomSpeedButton.WSRegisterClass;
918begin
919  inherited WSRegisterClass;
920  RegisterCustomSpeedButton;
921end;
922
923{------------------------------------------------------------------------------
924  Method: TCustomSpeedButton.WMLButtonUp
925  Params: Message
926  Returns: Nothing
927
928  Mouse event handler
929 ------------------------------------------------------------------------------}
930procedure TCustomSpeedButton.WMLButtonUp(var Message: TLMLButtonUp);
931var
932  OldState: TButtonState;
933  NeedClick: Boolean;
934begin
935  //DebugLn('TCustomSpeedButton.WMLButtonUp A ',DbgSName(Self),' csCaptureMouse=',DbgS(csCaptureMouse in ControlStyle),' csClicked=',DbgS(csClicked in ControlState));
936  if (csCaptureMouse in ControlStyle) and (mbLeft in CaptureMouseButtons) then
937  begin
938    {$IFDEF VerboseMouseCapture}
939    DebugLn('TCustomSpeedButton.WMLButtonUp ',Name,':',ClassName);
940    {$ENDIF}
941    MouseCapture := False;
942  end;
943
944  NeedClick := False;
945
946  if not (csDesigning in ComponentState) and FDragging then
947  begin
948    OldState := FState;
949    FDragging := False;
950
951    if FGroupIndex = 0 then
952    begin
953      FState := UpState[FMouseInControl];
954      if OldState <> FState then
955        Invalidate;
956    end
957    else
958    if (Message.XPos >= 0) and (Message.XPos < Width) and (Message.YPos >= 0) and (Message.YPos < Height) then
959    begin
960      SetDown(not FDown);
961      NeedClick := True;
962    end;
963  end;
964
965  DoMouseUp(Message, mbLeft);
966
967  if csClicked in ControlState then
968  begin
969    Exclude(FControlState, csClicked);
970    //DebugLn('TCustomSpeedButton.WMLButtonUp B ',dbgs(ClientRect.Left),',',dbgs(ClientRect.Top),',',dbgs(ClientRect.Right),',',dbgs(ClientRect.Bottom),' ',dbgs(Message.Pos.X),',',dbgs(Message.Pos.Y));
971    if PtInRect(ClientRect, SmallPointToPoint(Message.Pos)) then
972    begin
973      //DebugLn('TCustomSpeedButton.WMLButtonUp C');
974      // Important: Calling Click can invoke modal dialogs, so call this as last
975      NeedClick := False;
976      Click;
977    end;
978  end;
979
980  if NeedClick then
981    Click;
982  //DebugLn('TCustomSpeedButton.WMLButtonUp END');
983end;
984
985{------------------------------------------------------------------------------
986  Method: TCustomSpeedButton.SetLayout
987  Params: Value: new layout value
988  Returns:  nothing
989
990 ------------------------------------------------------------------------------}
991procedure TCustomSpeedButton.SetLayout(const Value : TButtonLayout);
992begin
993  if Value <> FLayout then
994  begin
995    FLayout:= Value;
996    Invalidate;
997  end;
998end;
999
1000{------------------------------------------------------------------------------
1001  Method: TCustomSpeedButton.SetTransparent
1002  Params: Value: new transparency value
1003  Returns:  nothing
1004
1005 ------------------------------------------------------------------------------}
1006procedure TCustomSpeedButton.SetTransparent(const AValue: boolean);
1007const
1008  MODE: array[Boolean] of TGlyphTransparencyMode = (gtmOpaque, gtmTransparent);
1009begin
1010  if AValue = Transparent then Exit;
1011
1012  if AValue then
1013    ControlStyle := ControlStyle - [csOpaque]
1014  else
1015    ControlStyle := ControlStyle + [csOpaque];
1016
1017  FGlyph.SetTransparentMode(MODE[AValue]);
1018  Invalidate;
1019end;
1020
1021{------------------------------------------------------------------------------
1022  Method: TCustomSpeedButton.CMButtonPressed
1023  Params: Message:
1024  Returns:  nothing
1025
1026 ------------------------------------------------------------------------------}
1027procedure TCustomSpeedButton.CMButtonPressed(var Message : TLMessage);
1028var
1029  Sender : TCustomSpeedButton;
1030begin
1031  if csDestroying in ComponentState then exit;
1032  if Message.WParam = WParam(FGroupIndex) then
1033  begin
1034    Sender := TCustomSpeedButton(Message.LParam);
1035    if Sender <> Self then
1036    begin
1037      if Sender.Down and FDown then
1038      begin
1039        FDown := False;
1040        FState := UpState[FMouseInControl];
1041        Invalidate;
1042      end;
1043      FAllowAllUp := Sender.AllowAllUp;
1044    end;
1045  end;
1046end;
1047
1048procedure TCustomSpeedButton.Loaded;
1049begin
1050  inherited Loaded;
1051  UpdateExclusive;
1052  if FDownLoaded then
1053    SetDown(FDownLoaded);
1054end;
1055
1056procedure TCustomSpeedButton.LoadGlyphFromResourceName(Instance: THandle; const AName: String);
1057begin
1058  Buttons.LoadGlyphFromResourceName(FGlyph, Instance, AName);
1059end;
1060
1061procedure TCustomSpeedButton.LoadGlyphFromLazarusResource(const AName: String);
1062begin
1063  Buttons.LoadGlyphFromLazarusResource(FGlyph, AName);
1064end;
1065
1066function TCustomSpeedButton.GetGlyphSize(Drawing: boolean; PaintRect: TRect): TSize;
1067var
1068  AImageRes: TScaledImageListResolution;
1069  AIndex: Integer;
1070  AEffect: TGraphicsDrawEffect;
1071begin
1072  FGlyph.GetImageIndexAndEffect(Low(TButtonState), Font.PixelsPerInch,
1073    GetCanvasScaleFactor, AImageRes, AIndex, AEffect);
1074  Result.CX := AImageRes.Width;
1075  Result.CY := AImageRes.Height;
1076end;
1077
1078function TCustomSpeedButton.GetImageIndex: TImageIndex;
1079begin
1080  Result := FGlyph.ExternalImageIndex;
1081end;
1082
1083function TCustomSpeedButton.GetImages: TCustomImageList;
1084begin
1085  Result := FGlyph.ExternalImages;
1086end;
1087
1088function TCustomSpeedButton.GetImageWidth: Integer;
1089begin
1090  Result := FGlyph.ExternalImageWidth;
1091end;
1092
1093function TCustomSpeedButton.GetTextSize(Drawing: boolean; PaintRect: TRect): TSize;
1094var
1095  TMP: String;
1096  Flags: Cardinal;
1097  DC: HDC;           // ~bk see : TCustomLabel.CalculateSize
1098  OldFont: HGDIOBJ;  //               "
1099begin
1100  if FShowCaption and (Caption <> '') then
1101  begin
1102    TMP := Caption;
1103    DeleteAmpersands(TMP);
1104    Flags := DT_CalcRect;
1105    if not Canvas.TextStyle.SingleLine then
1106      Inc(Flags, DT_WordBreak);
1107    DC := GetDC(Parent.Handle);
1108    try
1109      OldFont := SelectObject(DC, HGDIOBJ(Font.Reference.Handle));
1110      DrawText(DC, PChar(TMP), Length(TMP), PaintRect, Flags);
1111      SelectObject(DC, OldFont);
1112    finally
1113      ReleaseDC(Parent.Handle, DC);
1114    end;
1115    Result.CY := PaintRect.Bottom - PaintRect.Top;
1116    Result.CX := PaintRect.Right - PaintRect.Left;
1117  end
1118  else
1119  begin
1120    Result.CY:= 0;
1121    Result.CX:= 0;
1122  end;
1123end;
1124
1125function TCustomSpeedButton.GetTransparent: Boolean;
1126begin
1127  if FGlyph.TransparentMode = gtmGlyph then
1128    Result := FGlyph.FOriginal.Transparent
1129  else
1130    Result := FGlyph.TransparentMode = gtmTransparent;
1131end;
1132
1133function TCustomSpeedButton.DrawGlyph(ACanvas: TCanvas; const AClient: TRect;
1134  const AOffset: TPoint; AState: TButtonState; ATransparent: Boolean;
1135  BiDiFlags: Longint): TRect;
1136begin
1137  if Assigned(FGlyph) then
1138    Result := FGlyph.Draw(ACanvas, AClient, AOffset, AState, ATransparent, BiDiFlags,
1139      Font.PixelsPerInch, GetCanvasScaleFactor)
1140  else
1141    Result := Rect(0,0,0,0);
1142end;
1143
1144{------------------------------------------------------------------------------
1145  Method: TCustomSpeedButton.CMEnabledChanged
1146  Params: Message:
1147  Returns:  nothing
1148
1149 ------------------------------------------------------------------------------}
1150procedure TCustomSpeedButton.CMEnabledChanged(var Message: TLMessage);
1151Begin
1152  //Should create a new glyph based on the new state
1153  UpdateState(true);
1154end;
1155
1156{------------------------------------------------------------------------------
1157  Method: TCustomSpeedButton.MouseEnter
1158  Params:
1159  Returns:  nothing
1160
1161 ------------------------------------------------------------------------------}
1162procedure TCustomSpeedButton.MouseEnter;
1163begin
1164  if csDesigning in ComponentState then exit;
1165  if not FMouseInControl and IsEnabled and (GetCapture = 0) then
1166  begin
1167    FMouseInControl := True;
1168    UpdateState(true);
1169    inherited MouseEnter;
1170  end;
1171end;
1172
1173{------------------------------------------------------------------------------
1174  Method: TCustomSpeedButton.MouseLeave
1175  Params:
1176  Returns:  nothing
1177
1178 ------------------------------------------------------------------------------}
1179procedure TCustomSpeedButton.MouseLeave;
1180begin
1181  if csDesigning in ComponentState then exit;
1182  ///DebugLn(['TCustomSpeedButton.MouseLeave ',DbgSName(Self),' FMouseInControl=',FMouseInControl,' FDragging=',FDragging]);
1183  if FMouseInControl then
1184  begin
1185    FMouseInControl := False;
1186    if IsEnabled then
1187    begin
1188      if FDragging and (not MouseCapture) then
1189      begin
1190        // something fetched our mouse capture
1191        FDragging:=false;
1192      end;
1193      UpdateState(true);
1194      inherited MouseLeave;
1195    end;
1196  end;
1197end;
1198
1199{ TSpeedButtonActionLink }
1200
1201procedure TSpeedButtonActionLink.AssignClient(AClient: TObject);
1202begin
1203  inherited AssignClient(AClient);
1204  FClient := AClient as TCustomSpeedButton;
1205end;
1206
1207function TSpeedButtonActionLink.IsCheckedLinked: Boolean;
1208var
1209  SpeedButton: TCustomSpeedButton;
1210begin
1211  SpeedButton:=TCustomSpeedButton(FClient);
1212  Result := inherited IsCheckedLinked
1213            and (SpeedButton.GroupIndex <> 0)
1214            and SpeedButton.AllowAllUp
1215            and (SpeedButton.Down = (Action as TCustomAction).Checked);
1216end;
1217
1218function TSpeedButtonActionLink.IsGroupIndexLinked: Boolean;
1219var
1220  SpeedButton: TCustomSpeedButton;
1221begin
1222  SpeedButton:=TCustomSpeedButton(FClient);
1223  Result := (SpeedButton is TCustomSpeedButton) and
1224    (SpeedButton.GroupIndex = (Action as TCustomAction).GroupIndex);
1225end;
1226
1227function TSpeedButtonActionLink.IsImageIndexLinked: Boolean;
1228begin
1229  Result := inherited IsImageIndexLinked and
1230    (TSpeedButton(FClient).ImageIndex = (Action as TCustomAction).ImageIndex);
1231end;
1232
1233procedure TSpeedButtonActionLink.SetGroupIndex(Value: Integer);
1234begin
1235  if IsGroupIndexLinked then TCustomSpeedButton(FClient).GroupIndex := Value;
1236end;
1237
1238procedure TSpeedButtonActionLink.SetChecked(Value: Boolean);
1239begin
1240  if IsCheckedLinked then TCustomSpeedButton(FClient).Down := Value;
1241end;
1242
1243procedure TSpeedButtonActionLink.SetImageIndex(Value: Integer);
1244begin
1245  if IsImageIndexLinked then
1246    TSpeedButton(FClient).ImageIndex := Value;
1247end;
1248
1249
1250{$IFDEF ASSERT_IS_ON}
1251  {$UNDEF ASSERT_IS_ON}
1252  {$C-}
1253{$ENDIF}
1254
1255
1256// included by buttons.pp
1257