home *** CD-ROM | disk | FTP | other *** search
/ Chip 1999 January / Chip_1999-01_cd.bin / zkuste / delphi / D / WIDGETS.ZIP / WIDGET.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1998-07-25  |  20.5 KB  |  725 lines

  1. (*
  2.  
  3.                      TWidget: A Title Bar Button
  4.                      ~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5.  
  6.                Copyright 1998 by Robert R. Marsh, SJ
  7.           and the British Province of the Society of Jesus
  8.  
  9.                            rrm@sprynet.com
  10.                  http://home.sprynet.com/sprynet/rrm
  11.  
  12.    What another title bar button?! Yes ... but one that does the job
  13.    simply and cleanly.
  14.  
  15.    A Widget is drawn using the same technique windows itself uses to
  16.    draw its own frame buttons so they match exactly. The glyph of the
  17.    Widget comes from a truetype font. Windows uses the font Marlett for
  18.    its own glyphs but you can use any font you like. You can set the
  19.    glyph's color, the gap between it and the windows buttons, and even
  20.    give it a Hint. A Widget component editor makes choosing the glyph
  21.    very easy.
  22.  
  23.    Widgets are meant to adapt gracefully to any kind of window, even
  24.    coping with changes in caption size at run-time. You can have as many
  25.    Widgets in operation at any one time as you like. They are even
  26.    visible at design-time!
  27.  
  28.    The Widget components are released as freeware with no promise
  29.    expressed or implied about their suitability for any purpose. Use
  30.    them at your own risk. Please let me know though of any problems you
  31.    encounter.
  32.  
  33.    Parts of this code are inspired by the work of other authors whom I
  34.    wish to acknowledge gratefully:
  35.  
  36.         Brendan De Lumpa,
  37.         Sean Hsieh
  38.         The Programmers' Guild Guys
  39.  
  40.  
  41. *)
  42.  
  43. (*
  44.    This version (7/25/1998) has been enhanced to cope with forms with menus
  45.    ( thanks to Matt Hamilton for pointing out the problem ) and support has
  46.    been added for painting a bitmap instead of a font glyph ( thanks to John
  47.    Knipper ). There is now a published Image property. If no image is assigned
  48.    the glyph is drawn as before but if an image has been chosen it is painted.
  49.    The offset properties still apply. The WidgetEditor has been slightly
  50.    enhanced to let you choose the offset for images as well as glyphs but the
  51.    image property should be assigned via the Object Inspector. More work is
  52.    needed here!
  53.    There is now a public read-only WidgetRect property giving the location of
  54.    the widget in case you need to respond to a widget click in a way that needs
  55.    such information.
  56.    Also at John Knipper's suggestion TWidget now has a published PopupMenu
  57.    property. When this is set the menu pops up instead of the widget triggering
  58.    the OnClick event handler.
  59.    I've also changed the hint windows behavior to produce more conventional single-
  60.    line hints. 
  61. *)
  62.  
  63. unit widget;
  64.  
  65. interface
  66.  
  67. uses
  68.   Windows,
  69.   Messages,
  70.   Classes,
  71.   Graphics,
  72.   Controls,
  73.   ExtCtrls,
  74.   Menus;
  75.  
  76. type
  77.   TWidgetHint = class
  78.   private
  79.     FHintWindow: THintWindow;
  80.     FHint: string;
  81.     FStarted: boolean;
  82.     FShowing: boolean;
  83.     FXPos: integer;
  84.     FYPos: integer;
  85.     Timer: TTimer;
  86.   protected
  87.     procedure ShowWindow(Sender: TObject);
  88.     procedure HideWindow(Sender: TObject);
  89.   public
  90.     constructor Create;
  91.     destructor Destroy; override;
  92.     procedure Start;
  93.     procedure Stop;
  94.     property Hint: string read FHint write FHint;
  95.     property Started: boolean read FStarted;
  96.     property XPos: integer read FXPos write FXPos;
  97.     property YPos: integer read FYPos write FYPos;
  98.   end;
  99.  
  100. type
  101.   TWidget = class(TComponent)
  102.   private
  103.     OldWndProc: TFarProc;
  104.     NewWndProc: TFarProc;
  105.     FEnabled: boolean;
  106.     FFont: TFont;
  107.     FImage: TBitmap;
  108.     FGap: integer;
  109.     FGlyph: char;
  110.     FHint: string;
  111.     FShowHint: boolean;
  112.     FOffsetLeft: integer;
  113.     FOffsetTop: integer;
  114.     FPopupMenu: TPopupMenu;
  115.     FVisible: boolean;
  116.     FOnClick: TNotifyEvent;
  117.     FWidgetRect: TRect;
  118.     OldFont: TFont;
  119.     Pressed: boolean;
  120.     DrawPressed: boolean;
  121.     HintWindow: TWidgetHint;
  122.     WidgetNumber: integer;
  123.     RestoreTimer: TTimer;
  124.     procedure NewWndMethod(var msg: TMessage);
  125.     procedure RestoreHooks(Sender: TObject);
  126.   protected
  127.     procedure Click;
  128.     procedure CalculateWidgetArea;
  129.     procedure DrawWidget;
  130.     procedure SetEnabled(value: boolean);
  131.     procedure SetFont(Value: TFont);
  132.     procedure SetGap(Value: integer);
  133.     procedure SetGlyph(Value: char);
  134.     procedure SetImage(Value: TBitmap);
  135.     procedure SetOffsetLeft(value: integer);
  136.     procedure SetOffsetTop(value: integer);
  137.     procedure SetVisible(value: boolean);
  138.   public
  139.     constructor Create(AOwner: TComponent); override;
  140.     destructor Destroy; override;
  141.     property WidgetRect: TRect read FWidgetRect;
  142.   published
  143.     property Enabled: boolean read FEnabled write SetEnabled default true;
  144.     property Font: TFont read FFont write SetFont;
  145.     property Gap: integer read FGap write SetGap default 2;
  146.     property Glyph: char read FGlyph write SetGlyph;
  147.     property Hint: string read FHint write FHint;
  148.     property Image: TBitmap read FImage write SetImage;
  149.     property OffsetLeft: integer read FOffsetLeft write SetOffsetLeft;
  150.     property OffsetTop: integer read FOffsetTop write SetOffsetTop;
  151.     property PopupMenu: TPopupMenu read FPopupmenu write FPopupMenu;
  152.     property ShowHint: boolean read FShowHint write FShowHint default false;
  153.     property Visible: boolean read FVisible write SetVisible default true;
  154.     property OnClick: TNotifyEvent read FOnClick write FOnClick;
  155.   end;
  156.  
  157. implementation
  158.  
  159. uses
  160.   SysUtils,
  161.   Forms;
  162.  
  163. { TWidgetHint }
  164.  
  165. // provides a hint for the widget
  166.  
  167. constructor TWidgetHint.Create;
  168. begin
  169.   inherited Create;
  170.   FHintWindow := THintWindow.Create(nil);
  171.   Timer := TTimer.Create(nil);
  172.   Timer.Enabled := false;
  173.   FHintWindow.Brush.Color := Application.HintColor;
  174.   FStarted := false;
  175.   FShowing := false;
  176. end;
  177.  
  178. destructor TWidgetHint.Destroy;
  179. begin
  180.   Timer.Free;
  181.   FHintWindow.Free;
  182.   inherited Destroy;
  183. end;
  184.  
  185. procedure TWidgetHint.Start;
  186. begin
  187.   Timer.Interval := Application.HintPause;
  188.   Timer.OnTimer := ShowWindow;
  189.   Timer.Enabled := true;
  190.   FStarted := true;
  191.   FShowing := false;
  192. end;
  193.  
  194. procedure TWidgetHint.Stop;
  195. begin
  196.   Timer.Enabled := false;
  197.   if FShowing then
  198.     FHintWindow.ReleaseHandle;
  199.   FStarted := false;
  200.   FShowing := false;
  201. end;
  202.  
  203. // for displaying
  204.  
  205. function GetCursorHeightMargin: integer;
  206. var
  207.   IconInfo: TIconInfo;
  208.   BitmapInfoSize: {$IFDEF VER120}cardinal{$ELSE}integer{$ENDIF};
  209.   BitmapBitsSize: {$IFDEF VER120}cardinal{$ELSE}integer{$ENDIF};
  210.   Bitmap: PBitmapInfoHeader;
  211.   Bits: pointer;
  212.   BytesPerScanline, ImageSize: integer;
  213.  
  214.   function FindScanline(Source: pointer; MaxLen: Cardinal;
  215.     Value: Cardinal): Cardinal; assembler;
  216.   asm
  217.               PUSH    ECX
  218.               MOV     ECX,EDX
  219.               MOV     EDX,EDI
  220.               MOV     EDI,EAX
  221.               POP     EAX
  222.               REPE    SCASB
  223.               MOV     EAX,ECX
  224.               MOV     EDI,EDX
  225.   end;
  226.  
  227. begin
  228.   { Default value is entire icon height }
  229.   Result := GetSystemMetrics(SM_CYCURSOR);
  230.   if GetIconInfo(GetCursor, IconInfo) then
  231.   try
  232.     GetDIBSizes(IconInfo.hbmMask, BitmapInfoSize, BitmapBitsSize);
  233.     Bitmap := AllocMem(BitmapInfoSize + BitmapBitsSize);
  234.     try
  235.       Bits := pointer(longint(Bitmap) + BitmapInfoSize);
  236.       if GetDIB(IconInfo.hbmMask, 0, Bitmap^, Bits^) and
  237.         (Bitmap^.biBitCount = 1) then
  238.       begin
  239.           { Point Bits to the end of this bottom-up bitmap }
  240.         with Bitmap^ do
  241.         begin
  242.           BytesPerScanline := ((biWidth * biBitCount + 31) and not 31) div 8;
  243.           ImageSize := biWidth * BytesPerScanline;
  244.           Bits := pointer(integer(Bits) + BitmapBitsSize - ImageSize);
  245.             { Use the width to determine the height since another mask bitmap
  246.               may immediately follow }
  247.           Result := FindScanline(Bits, ImageSize, $FF);
  248.             { In case the and mask is blank, look for an empty scanline in the
  249.               xor mask. }
  250.           if (Result = 0) and (biHeight >= 2 * biWidth) then
  251.             Result := FindScanline(pointer(integer(Bits) - ImageSize),
  252.               ImageSize, $00);
  253.           Result := Result div BytesPerScanline;
  254.         end;
  255.         dec(Result, IconInfo.yHotSpot);
  256.       end;
  257.     finally
  258.       FreeMem(Bitmap, BitmapInfoSize + BitmapBitsSize);
  259.     end;
  260.   finally
  261.     if IconInfo.hbmColor <> 0 then DeleteObject(IconInfo.hbmColor);
  262.     if IconInfo.hbmMask <> 0 then DeleteObject(IconInfo.hbmMask);
  263.   end;
  264. end;
  265.  
  266. procedure TWidgetHint.ShowWindow(Sender: TObject);
  267. var
  268.   R: TRect;
  269. begin
  270.   Timer.Enabled := false;
  271.   if Hint = '' then
  272.     Exit;
  273.   // we need to calculate the size for the hint window
  274.   r := Rect(0, 0, Length(Hint), 0);
  275.   DrawText(FHintWindow.Canvas.Handle, PChar(Hint), -1, r, DT_CALCRECT or DT_LEFT or DT_NOPREFIX);
  276.   Inc(r.Right, 6);
  277.   Inc(r.Bottom, 2);
  278.   // add the height of the cursor
  279.   OffsetRect(R, XPos, YPos + GetCursorHeightMargin);
  280.   FHintWindow.ActivateHint(R, Hint);
  281.   Timer.Interval := Application.HintHidePause;
  282.   Timer.OnTimer := HideWindow;
  283.   Timer.Enabled := true;
  284.   FShowing := true;
  285. end;
  286.  
  287. procedure TWidgetHint.HideWindow(Sender: TObject);
  288. begin
  289.   Stop;
  290. end;
  291.  
  292. { TWidget }
  293.  
  294. constructor TWidget.Create(AOwner: TComponent);
  295. var
  296.   i: integer;
  297. begin
  298.   // ensure owner is a form
  299.   if (AOwner = nil) or not (AOwner is TForm) then
  300.     raise Exception.Create('A Widget must be owned by a form');
  301.   // first Widget is zero, second is one, etc.
  302.   WidgetNumber := 0;
  303.   for i := 1 to AOwner.ComponentCount do
  304.     if AOwner.Components[i - 1] is TWidget then
  305.       inc(WidgetNumber);
  306.   inherited Create(AOwner);
  307.   FEnabled := true;
  308.   FFont := TFont.Create;
  309.   FFont.Name := 'Marlett';
  310.   FFont.Color := clWindowText;
  311.   FFont.Style := [];
  312.   FGap := 2; // default spacing
  313.   FGlyph := 'v'; // a double up/down arrow in Marlett
  314.   FVisible := true;
  315.   OldFont := TFont.Create;
  316.   HintWindow := TWidgetHint.Create;
  317.   FImage := TBitMap.Create;
  318.   // subclass the owner to catch all its messages
  319.   NewWndProc := MakeObjectInstance(NewWndMethod);
  320.   OldWndProc := pointer(SetWindowLong((AOwner as TForm).Handle, gwl_WndProc, longint(NewWndProc)));
  321.   CalculateWidgetArea;
  322.   DrawWidget;
  323. end;
  324.  
  325. destructor TWidget.Destroy;
  326. begin
  327.   if Assigned(NewWndProc) and Assigned(Owner) then
  328.   begin
  329.     SetWindowLong((Owner as TForm).Handle, gwl_WndProc, longint(OldWndProc));
  330.     FreeObjectInstance(NewWndProc);
  331.   end;
  332.   HintWindow.Free;
  333.   FFont.Free;
  334.   OldFont.Free;
  335.   FImage.Free;
  336.   inherited Destroy;
  337. end;
  338.  
  339. // called when the form is being recreated during a borderstyle
  340. // change (for exmaple) -- rehooks the message trap
  341.  
  342. procedure TWidget.RestoreHooks(Sender: TObject);
  343. begin
  344.   RestoreTimer.Enabled := false;
  345.   RestoreTimer.Free;
  346.   OldWndProc := pointer(SetWindowLong((Owner as TForm).Handle, gwl_WndProc, longint(NewWndProc)));
  347.   CalculateWidgetArea;
  348.   DrawWidget;
  349. end;
  350.  
  351. const
  352.   wm_widgetupdate = wm_user + 1; // "widget draw thyself"
  353.  
  354. // This does all the work of handling the owner forms
  355. // messages. What it doesn't handle it passes on down
  356. // the chain of handlers.
  357.  
  358. procedure TWidget.NewWndMethod(var msg: TMessage);
  359.  
  360.   // pass the message on ...
  361.  
  362.   procedure DefHandler;
  363.   begin
  364.     msg.Result := CallWindowProc(OldWndProc, (Owner as TForm).Handle, msg.Msg, msg.wParam, msg.lParam);
  365.   end;
  366.  
  367.   // is the 'message' location within the widget?
  368.  
  369.   function InArea(InClient: boolean): boolean;
  370.   var
  371.     p: TPoint;
  372.   begin
  373.     p.X := Msg.lParamLo;
  374.     p.Y := smallint(Msg.lParamHi);
  375.     if not InClient then
  376.       ScreenToClient(TForm(Owner).Handle, p);
  377.     inc(p.X, 4);
  378.     inc(p.Y, (8 + FWidgetRect.Bottom - FWidgetRect.Top));
  379.     if ((Owner as TForm).Menu <> nil) and ((Owner as TForm).FormStyle <> fsMDIChild) then
  380.       inc(p.Y, GetSystemMetrics(SM_CYMENU));
  381.     Result := PtInRect(FWidgetRect, p);
  382.   end;
  383.  
  384. begin
  385.   case msg.Msg of
  386.     wm_ncpaint, wm_ncactivate:
  387.       begin
  388.         DefHandler;
  389.         // then post a message to yourself to redraw when possible
  390.         PostMessage((Owner as TForm).Handle, wm_widgetupdate, 0, WidgetNumber);
  391.       end;
  392.     wm_widgetupdate:
  393.       begin
  394.         if msg.lParam = WidgetNumber then
  395.           DrawWidget
  396.         else
  397.           DefHandler;
  398.       end;
  399.     wm_nclbuttondown, wm_nclbuttondblclk:
  400.       begin
  401.         if InArea(false) and Visible then
  402.           // going down on the widget
  403.         begin
  404.           if Enabled then
  405.           begin
  406.             SetCapture(TForm(Owner).Handle);
  407.             DrawPressed := true;
  408.             Pressed := true;
  409.             DrawWidget;
  410.           end;
  411.           // we're done
  412.           Msg.Result := 1;
  413.         end
  414.         else
  415.           DefHandler;
  416.       end;
  417.     wm_mousemove:
  418.       begin
  419.         // cancel any hint
  420.         if HintWindow.Started then
  421.           HintWindow.Stop;
  422.         if Pressed then
  423.           // the widget has been pressed
  424.         begin
  425.           if not InArea(true) then
  426.             // we're outside the widget
  427.           begin
  428.             // so show it 'unpressed'
  429.             if DrawPressed then
  430.             begin
  431.               DrawPressed := false;
  432.               DrawWidget;
  433.             end;
  434.           end
  435.           else
  436.             // we're inside the widget
  437.           begin
  438.             if not DrawPressed then
  439.               // make it draw 'pressed' again
  440.             begin
  441.               DrawPressed := true;
  442.               DrawWidget;
  443.             end;
  444.           end;
  445.           msg.Result := 1;
  446.         end
  447.         else
  448.           DefHandler;
  449.       end;
  450.     wm_ncmousemove:
  451.       begin
  452.         if InArea(false) then
  453.           // we're over the widget
  454.         begin
  455.           if not HintWindow.Started and ShowHint and Visible and Enabled then
  456.             // initiate the hint
  457.           begin
  458.             HintWindow.Hint := Hint;
  459.             HintWindow.XPos := TWMNCMouseMove(msg).XCursor;
  460.             HintWindow.YPos := TWMNCMouseMove(msg).YCursor;
  461.             HintWindow.Start;
  462.           end;
  463.         end
  464.         else
  465.         begin
  466.           // cancel any hint
  467.           if HintWindow.Started then
  468.             HintWindow.Stop;
  469.         end;
  470.         // pass it along
  471.         DefHandler;
  472.       end;
  473.     wm_lbuttonup, wm_lbuttondblclk:
  474.       begin
  475.         DrawPressed := false;
  476.         if Pressed then
  477.         begin
  478.           if InArea(true) then
  479.           begin
  480.             Click;
  481.           end;
  482.           DrawWidget;
  483.           msg.Result := 1;
  484.         end
  485.         else
  486.           DefHandler;
  487.         Pressed := false;
  488.         ReleaseCapture;
  489.       end;
  490.     wm_size, wm_windowposchanged, wm_settingchange, wm_stylechanged, wm_setfocus:
  491.       begin
  492.         // pass it along
  493.         DefHandler;
  494.         // and on the way back get the caption to redraw
  495.         CalculateWidgetArea;
  496.         DrawWidget;
  497.       end;
  498.     wm_destroy:
  499.       begin
  500.         if not (csDestroying in ComponentState) then
  501.         begin
  502.           // the form is being destroyed but only to recreate it
  503.           // we need to re hook everything in a short while
  504.           RestoreTimer := TTimer.Create(Application);
  505.           with RestoreTimer do
  506.           begin
  507.             OnTimer := RestoreHooks;
  508.             Interval := 1;
  509.             Enabled := true;
  510.           end;
  511.         end;
  512.         DefHandler;
  513.       end;
  514.   else
  515.     DefHandler;
  516.   end;
  517. end;
  518.  
  519. procedure TWidget.Click;
  520. begin
  521.   if Enabled then
  522.     if Assigned(FPopupMenu) then
  523.     begin
  524.       FPopupmenu.Popup(FWidgetRect.Left+(Owner as TForm).Left, FWidgetRect.Bottom+(Owner as TForm).Top)
  525.     end
  526.     else
  527.       if Assigned(FOnClick) then
  528.         FOnClick(Self);
  529. end;
  530.  
  531. // work out exactly where to put the widget
  532.  
  533. procedure TWidget.CalculateWidgetArea;
  534. var
  535.   xframe: integer;
  536.   yFrame: integer;
  537.   xsize: integer;
  538.   ysize: integer;
  539.   Icons: TBorderIcons;
  540.   Style: TFormBorderStyle;
  541. begin
  542.   if (Owner as TForm) = nil then
  543.     exit;
  544.   with (Owner as TForm) do
  545.   begin
  546.     // it will be different at design time
  547.     if (csDesigning in ComponentState) then
  548.     begin
  549.       Icons := [biSystemMenu, biMinimize, biMaximize];
  550.       Style := bsSizeable;
  551.     end
  552.     else
  553.     begin
  554.       Icons := BorderIcons;
  555.       Style := BorderStyle;
  556.     end;
  557.     if Style in [bsSizeToolWin, bsToolWindow] then
  558.     begin
  559.       if Style = bsToolWindow then
  560.         xframe := GetSystemMetrics(SM_CXFIXEDFRAME)
  561.       else
  562.         xframe := GetSystemMetrics(SM_CXSIZEFRAME);
  563.       if biSystemMenu in Icons then
  564.         inc(xframe, GetSystemMetrics(SM_CXSMSIZE));
  565.       if Style = bsToolWindow then
  566.         yframe := GetSystemMetrics(SM_CYFIXEDFRAME)
  567.       else
  568.         yframe := GetSystemMetrics(SM_CYSIZEFRAME);
  569.       ysize := GetSystemMetrics(SM_CYSMSIZE);
  570.       xsize := GetSystemMetrics(SM_CXSMSIZE);
  571.     end
  572.     else
  573.     begin
  574.       if Style in [bsSingle, bsSizeable, bsDialog] then
  575.       begin
  576.         if Style = bsSingle then
  577.           xframe := GetSystemMetrics(SM_CYFIXEDFRAME)
  578.         else
  579.           xframe := GetSystemMetrics(SM_CXSIZEFRAME);
  580.         if biSystemMenu in Icons then
  581.         begin
  582.           inc(xframe, GetSystemMetrics(SM_CXSIZE));
  583.           if (Style <> bsDialog) and (Icons * [biMinimize, biMaximize] <> []) then
  584.             inc(xframe, GetSystemMetrics(SM_CXSIZE) * 2)
  585.           else
  586.             if biHelp in Icons then
  587.               inc(xframe, GetSystemMetrics(SM_CXSIZE));
  588.         end;
  589.         if Style in [bsSingle, bsDialog] then
  590.           yframe := GetSystemMetrics(SM_CYFIXEDFRAME)
  591.         else
  592.           yframe := GetSystemMetrics(SM_CYSIZEFRAME);
  593.         ysize := GetSystemMetrics(SM_CYSIZE);
  594.         xsize := GetSystemMetrics(SM_CXSIZE);
  595.       end;
  596.     end;
  597.     // each successive Widget is a little further left
  598.     inc(xframe, WidgetNumber * (Gap - 2 + xsize));
  599.     // finally we calculate the size and position of the widget
  600.     FWidgetRect := Bounds(Width - xFrame - xSize + 4 - Gap, yFrame + 2, xSize - 2, ySize - 4);
  601.   end;
  602. end;
  603.  
  604. procedure TWidget.DrawWidget;
  605. var
  606.   WidgetState: integer;
  607.   R: TRect;
  608.   RB: TRect;
  609. begin
  610.   if ((Owner as TForm) = nil) or not Visible then
  611.     exit;
  612.   with (Owner as TForm) do
  613.   begin
  614.     //Get the handle to the canvas
  615.     Canvas.Handle := GetWindowDC(Handle);
  616.     try
  617.       // save the font
  618.       OldFont.Assign(Canvas.Font);
  619.       // use the widget's font
  620.       Canvas.Font.Assign(FFont);
  621.       try
  622.         Canvas.Brush.Color := clBtnFace;
  623.         SetBkMode(Canvas.Handle, TRANSPARENT);
  624.         WidgetState := DFCS_BUTTONPUSH;
  625.         if not FEnabled then
  626.         begin
  627.           WidgetState := WidgetState or DFCS_INACTIVE;
  628.           DrawPressed := false;
  629.           Canvas.Font.Color := clGrayText;
  630.         end;
  631.         if DrawPressed then
  632.           WidgetState := WidgetState or DFCS_PUSHED;
  633.         // this is how windows draws its own frame buttons
  634.         DrawFrameControl(Canvas.Handle, FWidgetRect, DFC_BUTTON, WidgetState);
  635.         // define a smaller area to put the glyph in
  636.         R := FWidgetRect;
  637.         InflateRect(R, -2, -2);
  638.         if DrawPressed then
  639.           OffsetRect(R, 1, 1);
  640.         OffsetRect(R, OffsetLeft, OffsetTop);
  641.         if FImage.Empty then
  642.         begin
  643.           // choose a font size to fit
  644.           Canvas.Font.Height := (R.Top - R.Bottom - 1);
  645.           DrawText(Canvas.Handle, pchar(string(FGlyph)), 1, R, DT_CENTER or DT_VCENTER or DT_NOCLIP);
  646.         end
  647.         else
  648.         begin
  649.           RB := FImage.Canvas.ClipRect;
  650.           Canvas.BrushCopy(R, FImage, RB, FImage.TransparentColor);
  651.         end;
  652.       finally
  653.         Canvas.Font.Assign(OldFont);
  654.       end;
  655.     finally
  656.       ReleaseDC(Handle, Canvas.Handle);
  657.       Canvas.Handle := 0;
  658.     end;
  659.   end;
  660. end;
  661.  
  662. procedure TWidget.SetEnabled(Value: boolean);
  663. begin
  664.   if FEnabled <> value then
  665.   begin
  666.     FEnabled := value;
  667.     CalculateWidgetArea;
  668.     DrawWidget;
  669.   end;
  670. end;
  671.  
  672. procedure TWidget.SetFont(Value: TFont);
  673. begin
  674.   FFont.Assign(Value);
  675.   CalculateWidgetArea;
  676.   DrawWidget;
  677. end;
  678.  
  679. procedure TWidget.SetGap(Value: integer);
  680. begin
  681.   FGap := Value;
  682.   CalculateWidgetArea;
  683.   DrawWidget;
  684. end;
  685.  
  686. procedure TWidget.SetGlyph(Value: char);
  687. begin
  688.   FGlyph := Value;
  689.   CalculateWidgetArea;
  690.   DrawWidget;
  691. end;
  692.  
  693. procedure TWidget.SetImage(Value: TBitmap);
  694. begin
  695.   FImage.Assign(Value);
  696.   DrawWidget;
  697. end;
  698.  
  699. procedure TWidget.SetOffsetLeft(value: integer);
  700. begin
  701.   FOffsetLeft := value;
  702.   CalculateWidgetArea;
  703.   DrawWidget;
  704. end;
  705.  
  706. procedure TWidget.SetOffsetTop(value: integer);
  707. begin
  708.   FOffsetTop := value;
  709.   CalculateWidgetArea;
  710.   DrawWidget;
  711. end;
  712.  
  713. procedure TWidget.SetVisible(Value: boolean);
  714. begin
  715.   if FVisible <> value then
  716.   begin
  717.     FVisible := value;
  718.     CalculateWidgetArea;
  719.     DrawWidget;
  720.   end;
  721. end;
  722.  
  723. end.
  724.  
  725.