{ ***************************************************************************** * Gtk2WSButtons.pp * * ---------------- * * * * * ***************************************************************************** ***************************************************************************** This file is part of the Lazarus Component Library (LCL) See the file COPYING.modifiedLGPL.txt, included in this distribution, for details about the license. ***************************************************************************** } unit Gtk2WSButtons; {$mode objfpc}{$H+} interface uses // RTL Classes, glib2, gtk2, gdk2, gdk2pixbuf, // LCL LCLType, Controls, Buttons, Graphics, ImgList, // LazUtils GraphType, {$IFDEF DebugLCLComponents} LazLoggerBase, {$ENDIF} // WS WSButtons, WSLCLClasses, WSProc, Gtk2Def; type PBitBtnWidgetInfo = ^TBitBtnWidgetInfo; TBitBtnWidgetInfo = record ImageWidget: Pointer; LabelWidget: Pointer; SetLayoutCalled: boolean; end; { TGtk2WSBitBtn } TGtk2WSBitBtn = class(TWSBitBtn) private class procedure BuildWidget(ABitBtn: TCustomBitBtn; MainWidget: PGtkWidget; ABitBtnInfo: PBitBtnWidgetInfo; const ACaption: String); class procedure UnparentWidget(Widget: PGtkWidget); class procedure UpdateImageWidget(ImageWidget: PGtkImage; Bitmap: TBitmap); class procedure UpdateLabelFont(LabelWidget: PGtkWidget; Font: TFont); protected class function UpdateGlyph(const ABitBtn: TCustomBitBtn; BitBtnInfo: PBitBtnWidgetInfo; const AValue: TButtonGlyph; const AButtonState: TButtonState): Boolean; class procedure UpdateMargin(const ABitBtn: TCustomBitBtn; const AAlignWidget: PGtkAlignment; const AMargin: Integer); class procedure SetCallbacks(const AGtkWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo); virtual; published class function CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle; override; class procedure SetGlyph(const ABitBtn: TCustomBitBtn; const AValue: TButtonGlyph); override; class procedure SetLayout(const ABitBtn: TCustomBitBtn; const {%H-}AValue: TButtonLayout); override; class procedure SetMargin(const ABitBtn: TCustomBitBtn; const AValue: Integer); override; class procedure SetSpacing(const ABitBtn: TCustomBitBtn; const AValue: Integer); override; class procedure SetText(const AWinControl: TWinControl; const AText: String); override; class procedure SetColor(const AWinControl: TWinControl); override; class procedure SetFont(const AWinControl: TWinControl; const AFont: TFont); override; end; TGtk2WSBitBtnClass = class of TGtk2WSBitBtn; { TGtk2WSSpeedButton } TGtk2WSSpeedButton = class(TWSSpeedButton) private protected public end; implementation uses Gtk2Proc, Gtk2Int, Gtk2WSStdCtrls; const GtkStateToButtonState: array[GTK_STATE_NORMAL..GTK_STATE_INSENSITIVE] of TButtonState = ( {GTK_STATE_NORMAL } bsUp, {GTK_STATE_ACTIVE } bsDown, {GTK_STATE_PRELIGHT } bsHot, {GTK_STATE_SELECTED } bsDown, {GTK_STATE_INSENSITIVE} bsDisabled ); type TCustomBitBtnAccess = class(TCustomBitBtn) end; procedure GtkWSBitBtn_StateChanged(AWidget: PGtkWidget; {%H-}AState: TGtkStateType; AInfo: PWidgetInfo); cdecl; var BitBtn: TCustomBitBtnAccess; begin //WriteLn(Astate, ' :: ', GTK_WIDGET_STATE(AWidget)); BitBtn := TCustomBitBtnAccess(AInfo^.LCLObject); TGtk2WSBitBtn.UpdateGlyph(BitBtn, PBitBtnWidgetInfo(AInfo^.UserData), BitBtn.FButtonGlyph, GtkStateToButtonState[GTK_WIDGET_STATE(AWidget)]); end; { TGtk2WSBitBtn } class procedure TGtk2WSBitBtn.BuildWidget(ABitBtn: TCustomBitBtn; MainWidget: PGtkWidget; ABitBtnInfo: PBitBtnWidgetInfo; const ACaption: String); var AlignWidget: PGtkWidget; LabelWidget: PGtkWidget; ImageWidget: PGtkWidget; ContentWidget: PGtkWidget; begin ImageWidget := ABitBtnInfo^.ImageWidget; // keep a temporary reference to avoid the destruction and remove ImageWidget if ImageWidget <> nil then begin g_object_ref(ImageWidget); UnparentWidget(ImageWidget); end; // clear the widget (will destroy the children) ContentWidget := gtk_bin_get_child(PGtkBin(MainWidget)); if ContentWidget <> nil then gtk_container_remove(PGtkContainer(MainWidget), ContentWidget); ContentWidget := nil; // setup label LabelWidget := nil; if ACaption <> '' then begin LabelWidget := gtk_label_new(nil); GTK2WidgetSet.SetLabelCaption(PGtkLabel(LabelWidget), ACaption); UpdateLabelFont(LabelWidget, ABitBtn.Font); end; // button with image and label if (ImageWidget <> nil) and (LabelWidget <> nil) then begin if (ABitBtn.Layout in [blGlyphLeft, blGlyphRight]) then ContentWidget := gtk_hbox_new(False, ABitBtn.Spacing) else ContentWidget := gtk_vbox_new(False, ABitBtn.Spacing); if (ABitBtn.Layout in [blGlyphLeft, blGlyphTop]) then gtk_box_pack_start(PGtkBox(ContentWidget), ImageWidget, True, True, 0) else gtk_box_pack_end(PGtkBox(ContentWidget), ImageWidget, True, True, 0); if (ABitBtn.Layout in [blGlyphRight, blGlyphBottom]) then gtk_box_pack_start(PGtkBox(ContentWidget), LabelWidget, True, True, 0) else gtk_box_pack_end(PGtkBox(ContentWidget), LabelWidget, True, True, 0); end else begin // only image or label (or none) if ImageWidget <> nil then ContentWidget := ImageWidget else if LabelWidget <> nil then ContentWidget := LabelWidget; end; // setup align and build the widget AlignWidget := gtk_alignment_new(0, 0, 0, 0); UpdateMargin(ABitBtn, PGtkAlignment(AlignWidget), ABitBtn.Margin); gtk_container_add(PGtkContainer(MainWidget), AlignWidget); if ContentWidget <> nil then gtk_container_add(PGtkContainer(AlignWidget), ContentWidget); gtk_widget_show_all(AlignWidget); // Release the temporary reference if ImageWidget <> nil then g_object_unref(ImageWidget); ABitBtnInfo^.LabelWidget := LabelWidget; end; class procedure TGtk2WSBitBtn.UnparentWidget(Widget: PGtkWidget); var ParentWidget: PGtkWidget; begin ParentWidget := gtk_widget_get_parent(Widget); if ParentWidget <> nil then gtk_container_remove(PGtkContainer(ParentWidget), Widget); end; class function TGtk2WSBitBtn.CreateHandle(const AWinControl: TWinControl; const AParams: TCreateParams): TLCLIntfHandle; var BitBtn: TCustomBitBtn absolute AWinControl; WidgetInfo: PWidgetInfo; BitBtnInfo: PBitBtnWidgetInfo; Allocation: TGTKAllocation; begin Result := TLCLIntfHandle({%H-}PtrUInt(gtk_button_new)); if Result = 0 then Exit; {$IFDEF DebugLCLComponents} DebugGtkWidgets.MarkCreated(Pointer(Result),DbgSName(AWinControl)); {$ENDIF} WidgetInfo := CreateWidgetInfo({%H-}Pointer(Result), BitBtn, AParams); New(BitBtnInfo); FillChar(BitBtnInfo^, SizeOf(BitBtnInfo^), 0); WidgetInfo^.UserData := BitBtnInfo; WidgetInfo^.DataOwner := True; gtk_widget_show({%H-}PGtkWidget(Result)); Allocation.X := AParams.X; Allocation.Y := AParams.Y; Allocation.Width := AParams.Width; Allocation.Height := AParams.Height; gtk_widget_size_allocate({%H-}PGtkWidget(Result), @Allocation); Set_RC_Name(AWinControl, {%H-}PGtkWidget(Result)); if not AWinControl.HandleObjectShouldBeVisible and not (csDesigning in AWinControl.ComponentState) then gtk_widget_hide({%H-}PGtkWidget(Result)); SetCallbacks({%H-}PGtkWidget(Result), WidgetInfo); end; class procedure TGtk2WSBitBtn.SetGlyph(const ABitBtn: TCustomBitBtn; const AValue: TButtonGlyph); var MainWidget: PGtkWidget; WidgetInfo: PWidgetInfo; BitBtnInfo: PBitBtnWidgetInfo; BuildNeeded: Boolean; begin if not WSCheckHandleAllocated(ABitBtn, 'SetGlyph') then Exit; MainWidget := {%H-}PGtkWidget(ABitBtn.Handle); WidgetInfo := GetWidgetInfo(MainWidget); BitBtnInfo := WidgetInfo^.UserData; BuildNeeded := UpdateGlyph(ABitBtn, BitBtnInfo, AValue, GtkStateToButtonState[GTK_WIDGET_STATE(MainWidget)]); if BuildNeeded and BitBtnInfo^.SetLayoutCalled then BuildWidget(ABitBtn, MainWidget, BitBtnInfo, ABitBtn.Caption); end; class procedure TGtk2WSBitBtn.SetLayout(const ABitBtn: TCustomBitBtn; const AValue: TButtonLayout); var MainWidget: PGtkWidget; WidgetInfo: PWidgetInfo; BitBtnInfo: PBitBtnWidgetInfo; begin if not WSCheckHandleAllocated(ABitBtn, 'SetLayout') then Exit; MainWidget := {%H-}Pointer(ABitBtn.Handle); WidgetInfo := GetWidgetInfo(MainWidget); BitBtnInfo := WidgetInfo^.UserData; BitBtnInfo^.SetLayoutCalled:=true; BuildWidget(ABitBtn, MainWidget, BitBtnInfo, ABitBtn.Caption); end; class procedure TGtk2WSBitBtn.SetMargin(const ABitBtn: TCustomBitBtn; const AValue: Integer); var MainWidget: PGtkWidget; AlignWidget: PGtkAlignment; begin if not WSCheckHandleAllocated(ABitBtn, 'SetMargin') then Exit; MainWidget := {%H-}PGtkWidget(ABitBtn.Handle); AlignWidget := PGtkAlignment(gtk_bin_get_child(PGtkBin(MainWidget))); if GTK_IS_ALIGNMENT(AlignWidget) then UpdateMargin(ABitBtn, AlignWidget, AValue); end; class procedure TGtk2WSBitBtn.SetSpacing(const ABitBtn: TCustomBitBtn; const AValue: Integer); var MainWidget: PGtkWidget; ChildWidget: PGtkWidget; begin if not WSCheckHandleAllocated(ABitBtn, 'SetSpacing') then Exit; MainWidget := {%H-}Pointer(ABitBtn.Handle); ChildWidget := gtk_bin_get_child(PGtkBin(MainWidget)); if GTK_IS_ALIGNMENT(ChildWidget) then begin ChildWidget := gtk_bin_get_child(PGtkBin(ChildWidget)); if GTK_IS_BOX(ChildWidget) then gtk_box_set_spacing(PGtkBox(ChildWidget), AValue); end; end; class procedure TGtk2WSBitBtn.SetText(const AWinControl: TWinControl; const AText: String); var MainWidget: PGtkWidget; LabelWidget: PGtkWidget; WidgetInfo: PWidgetInfo; BitBtnInfo: PBitBtnWidgetInfo; BuildNeeded: Boolean; begin MainWidget := {%H-}Pointer(AWinControl.Handle); WidgetInfo := GetWidgetInfo(MainWidget); BitBtnInfo := WidgetInfo^.UserData; if (not BitBtnInfo^.SetLayoutCalled) or not WSCheckHandleAllocated(AWincontrol, 'SetText') then Exit; LabelWidget := BitBtnInfo^.LabelWidget; BuildNeeded := (LabelWidget = nil) xor (AText = ''); if BuildNeeded then BuildWidget(TBitBtn(AWinControl), MainWidget, BitBtnInfo, AText) else begin if LabelWidget <> nil then Gtk2WidgetSet.SetLabelCaption(PGtkLabel(LabelWidget), AText); end; end; class procedure TGtk2WSBitBtn.SetColor(const AWinControl: TWinControl); var Widget: PGTKWidget; begin if not AWinControl.HandleAllocated then exit; Widget:= {%H-}PGtkWidget(AWinControl.Handle); Gtk2WidgetSet.SetWidgetColor(Widget, clNone, AWinControl.color, [GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED]); end; class procedure TGtk2WSBitBtn.SetFont(const AWinControl: TWinControl; const AFont: TFont); var WidgetInfo: PWidgetInfo; BitBtnInfo: PBitBtnWidgetInfo; LabelWidget: PGTKWidget; begin if not AWinControl.HandleAllocated then exit; WidgetInfo := GetWidgetInfo({%H-}PGtkWidget(AWinControl.Handle)); BitBtnInfo := WidgetInfo^.UserData; LabelWidget := BitBtnInfo^.LabelWidget; if LabelWidget <> nil then UpdateLabelFont(LabelWidget, AFont); end; { UpdateGlyph: update the bitbtn glyph and returns if the structure changed } class function TGtk2WSBitBtn.UpdateGlyph(const ABitBtn: TCustomBitBtn; BitBtnInfo: PBitBtnWidgetInfo; const AValue: TButtonGlyph; const AButtonState: TButtonState): Boolean; var ShowGlyph: Boolean; ImageWidget: PGtkWidget; AGlyph: TBitmap; AIndex: Integer; AEffect: TGraphicsDrawEffect; AImageRes: TScaledImageListResolution; begin ShowGlyph := ABitBtn.CanShowGlyph(True); if ShowGlyph then begin ImageWidget := BitBtnInfo^.ImageWidget; AGlyph := TBitmap.Create; AValue.GetImageIndexAndEffect(AButtonState, ABitBtn.Font.PixelsPerInch, ABitBtn.GetCanvasScaleFactor, AImageRes, AIndex, AEffect); if (AIndex <> -1) and AImageRes.Valid then AImageRes.GetBitmap(AIndex, AGlyph, AEffect); ShowGlyph := not AGlyph.Empty; if ShowGlyph then begin if ImageWidget = nil then ImageWidget := gtk_image_new; UpdateImageWidget(PGtkImage(ImageWidget), AGlyph); end; AGlyph.Destroy; end else ImageWidget := nil; // Return true if the image was removed or added Result := ImageWidget <> BitBtnInfo^.ImageWidget; if Result then begin // BitBtnInfo^.ImageWidget <> nil -> remove from parent if not ShowGlyph then UnparentWidget(BitBtnInfo^.ImageWidget); BitBtnInfo^.ImageWidget := ImageWidget; end; end; class procedure TGtk2WSBitBtn.UpdateMargin(const ABitBtn: TCustomBitBtn; const AAlignWidget: PGtkAlignment; const AMargin: Integer); begin if AMargin < 0 then gtk_alignment_set (AAlignWidget, 0.5, 0.5, 0.0, 0.0) else begin case ABitBtn.Layout of blGlyphLeft: begin gtk_alignment_set(AAlignWidget, 0, 0.5, 0, 0); gtk_alignment_set_padding(AAlignWidget, 0, 0, AMargin, 0); end; blGlyphRight: begin gtk_alignment_set(AAlignWidget, 1, 0.5, 0, 0); gtk_alignment_set_padding(AAlignWidget, 0, 0, 0, AMargin); end; blGlyphTop: begin gtk_alignment_set(AAlignWidget, 0.5, 0, 0, 0); gtk_alignment_set_padding(AAlignWidget, AMargin, 0, 0, 0); end; blGlyphBottom: begin gtk_alignment_set(AAlignWidget, 0.5, 1, 0, 0); gtk_alignment_set_padding(AAlignWidget, 0, AMargin, 0, 0); end; end; end; end; class procedure TGtk2WSBitBtn.SetCallbacks(const AGtkWidget: PGtkWidget; const AWidgetInfo: PWidgetInfo); begin TGtk2WSButton.SetCallbacks(AGtkWidget, AWidgetInfo); SignalConnect(AGtkWidget, 'state-changed', @GtkWSBitBtn_StateChanged, AWidgetInfo); end; class procedure TGtk2WSBitBtn.UpdateImageWidget(ImageWidget: PGtkImage; Bitmap: TBitmap); var GDIObject: PGDIObject; Pixbuf: PGdkPixbuf; Mask: PGdkBitmap; begin GDIObject := {%H-}PGDIObject(Bitmap.Handle); Mask := nil; Pixbuf := nil; if GDIObject^.GDIBitmapType = gbPixbuf then Pixbuf := GDIObject^.GDIPixbufObject else Mask := CreateGdkMaskBitmap(Bitmap.Handle, Bitmap.MaskHandle); if Pixbuf <> nil then gtk_image_set_from_pixbuf(ImageWidget, Pixbuf) else gtk_image_set_from_pixmap(ImageWidget, GDIObject^.GDIPixmapObject.Image, Mask); if Mask <> nil then g_object_unref(Mask); end; class procedure TGtk2WSBitBtn.UpdateLabelFont(LabelWidget: PGtkWidget; Font: TFont); begin Gtk2WidgetSet.SetWidgetColor(LabelWidget, Font.Color, clNone, [GTK_STATE_NORMAL,GTK_STATE_ACTIVE,GTK_STATE_PRELIGHT,GTK_STATE_SELECTED]); Gtk2WidgetSet.SetWidgetFont(LabelWidget, Font); end; end.