{************************************************}
{                                                }
{   Turbo Pascal for Windows                     }
{   Demo library (DLL)                           }
{   Copyright (c) 1992 by Borland International  }
{                                                }
{************************************************}

library BitBtn;

uses WinTypes, WinProcs, Strings, CustCntl, BitBtnCo;

{$R BITBTN.RES}

{ ==============================================================
  Bitmaped button custom control.
  ============================================================== }

const
  ofReserved    = 0;  { Used by the dialog manager }
  ofState       = 2;
  ofDownBits    = 4;
  ofUpBits      = 6;
  ofFocUpBits   = 8;
  ofSize        = 10; { Amount of window extra bytes to use }

const
  bdBorderWidth = 1;

const
  bsDisabled    = $0001;
  bsFocus       = $0002;
  bsKeyDown     = $0004;
  bsMouseDown   = $0008;
  bsMouseUpDown = $0010;
  bsDefault     = $0020;

{ GetAppInstance -----------------------------------------------
    Returns a handle to the current client application.
  -------------------------------------------------------------- }
function GetAppInstance: THandle; near; assembler;
asm
	PUSH	SS
	CALL	GlobalHandle
end;

{ IsWorkshopWindow ---------------------------------------------
    Returns true if the window belongs to Resource Workshop.
    Used to determine if the control is being edited; allowing
    the LoadResRW function to be called.
  -------------------------------------------------------------- }
function IsWorkshopWindow(Wnd: HWnd): Boolean;
var
  Parent: HWnd;
  ClassName: array[0..80] of Char;
begin
  Parent := Wnd;
  repeat
    Wnd := Parent;
    Parent := GetParent(Wnd);
  until Parent = 0;
  GetClassName(Wnd, ClassName, SizeOf(ClassName));
  IsWorkshopWindow := StrComp(ClassName, 'rwswnd') = 0;
end;

{ LoadResRW ----------------------------------------------------
    Load a resource from Resource Workshop. Initialized by
    ListClasses below.
  -------------------------------------------------------------- }
var
  LoadResRW: TLoad;

{ LoadBitmapRW -------------------------------------------------
    Load a bitmap from Resource Workshop.  *MUST* be called from
    inside resource workshop (IsWorkshopWindow must be true).
  -------------------------------------------------------------- }
function LoadBitmapRW(szTitle: PChar): HBitmap;
var
  Res: THandle;
  Bits: PBitMapInfoHeader;
  DC: HDC;
  nColors: Integer;
  Ret: HBitmap;

function GetDInColors(BitCount: Integer): Integer;
begin
  case BitCount of
    1, 3, 4, 8: GetDInColors := 1 shl BitCount;
  else
    GetDInColors := 0;
  end;
end;

begin
  LoadBitmapRW := 0;
  Res := LoadResRW(rt_Bitmap, szTitle);
  if Res <> 0 then
  begin
    Bits := GlobalLock(Res);
    if Bits^.biSize = SizeOf(TBitMapInfoHeader) then
    begin
      nColors := GetDInColors(Bits^.biBitCount);
      DC := GetDC(0);
      if DC <> 0 then
      begin
	LoadBitmapRW := CreateDIBitmap(DC, Bits^, cbm_Init,
	  Pointer(LongInt(Bits) + SizeOf(Bits^) +
	  nColors * SizeOf(TRGBQuad)), PBitmapInfo(Bits)^,
	  dib_RGB_Colors);
	ReleaseDC(0, DC);
      end;
    end;
    GlobalUnlock(Res);
    GlobalFree(Res);
  end;
end;

{ BitButtonWinFn -----------------------------------------------
    Button window procedure.
  -------------------------------------------------------------- }
function BitButtonWinFn(HWindow: HWnd; Message: Word; wParam: Word;
  lParam: Longint): Longint; export;
var
  DC: HDC;
  BitsNumber: Integer;
  Bitmap: TBitmap;
  Rect: TRect;
  Pt: TPoint;
  PS: TPaintStruct;

{ Get ----------------------------------------------------------
    Get a window instance word.
  -------------------------------------------------------------- }
function Get(Ofs: Integer): Word;
begin
  Get := GetWindowWord(HWindow, Ofs);
end;

{ SetWord ------------------------------------------------------
    Set the value of a window instance word.
  -------------------------------------------------------------- }
procedure SetWord(Ofs: Integer; Val: Word);
begin
  SetWindowWord(HWindow, Ofs, Val);
end;

{ State --------------------------------------------------------
    Get the button's state word.
  -------------------------------------------------------------- }
function State: Word;
begin
  State := Get(ofState);
end;

{ DownBits -----------------------------------------------------
    Get the "down" bitmap of the button.
  -------------------------------------------------------------- }
function DownBits: Word;
begin
  DownBits := Get(ofDownBits);
end;

{ UpBits -------------------------------------------------------
    Get the "up" bitmap of the button.
  -------------------------------------------------------------- }
function UpBits: Word;
begin
  UpBits := Get(ofUpBits);
end;

{ FocUpBits ----------------------------------------------------
    Get the "focused up" bitmap of the button.
  -------------------------------------------------------------- }
function FocUpBits: Word;
begin
  FocUpBits := Get(ofFocUpBits);
end;

{ GetState -----------------------------------------------------
    Get the value of a state bit.
  -------------------------------------------------------------- }
function GetState(AState: Word): Boolean;
begin
  GetState := (State and AState) = AState;
end;

{ Paint --------------------------------------------------------
    Paint the button.  Called in responce to a WM_PAINT message
    and whenever the button changes state (called by Repaint).
  -------------------------------------------------------------- }
procedure Paint(DC: HDC);
const
  coGray = $00C0C0C0;
var
  MemDC: HDC;
  Bits, Oldbitmap: HBitmap;
  BorderBrush, OldBrush: HBrush;
  LogBrush: TLogBrush;
  DisableBits: HBitmap;
  Frame: TRect;
  Height, Width: Integer;
begin
  if (State and (bsMouseDown + bsKeyDown) <> 0) and
      not GetState(bsMouseUpDown) then
    Bits := DownBits
  else
    if GetState(bsFocus) then
      Bits := FocUpBits
    else
      Bits := UpBits;

  { Draw border }
  GetClientRect(HWindow, Frame);
  Height := Frame.bottom - Frame.top;
  Width := Frame.right - Frame.left;

  if GetState(bsDefault) then
    BorderBrush := GetStockObject(Black_Brush)
  else BorderBrush := GetStockObject(White_Brush);
  OldBrush := SelectObject(DC, BorderBrush);
  PatBlt(DC, Frame.left, Frame.top, Width, bdBorderWidth, PatCopy);
  PatBlt(DC, Frame.left, Frame.top, bdBorderWidth, Height, PatCopy);
  PatBlt(DC, Frame.left, Frame.bottom - bdBorderWidth, Width,
    bdBorderWidth, PatCopy);
  PatBlt(DC, Frame.right - bdBorderWidth, Frame.top, bdBorderWidth,
    Height, PatCopy);
  SelectObject(DC, OldBrush);

  { Draw bitmap }
  MemDC := CreateCompatibleDC(DC);
  OldBitmap := SelectObject(MemDC, Bits);
  GetObject(Bits, Sizeof(Bitmap), @Bitmap);
  if GetState(bsDisabled) then
  begin
    { Gray out the button }
    OldBrush := SelectObject(DC, CreateSolidBrush(coGray));
    PatBlt(DC, bdBorderWidth, bdBorderWidth, Bitmap.bmWidth,
      Bitmap.bmHeight, PatCopy);
    DeleteObject(SelectObject(DC, OldBrush));

    { Draw the bitmap through a checked brush }
    LogBrush.lbStyle := bs_Pattern;
    LogBrush.lbHatch := LoadBitmap(HInstance, MakeIntResource(btDisableBits));
    OldBrush := SelectObject(DC, CreateBrushIndirect(LogBrush));
    BitBlt(DC, bdBorderWidth, bdBorderWidth, Bitmap.bmWidth,
      Bitmap.bmHeight, MemDC, 0, 0, $00A803A9 {DPSoa});
    DeleteObject(SelectObject(DC, OldBrush));
    DeleteObject(LogBrush.lbHatch);
  end
  else
    BitBlt(DC, bdBorderWidth, bdBorderWidth, Bitmap.bmWidth,
      Bitmap.bmHeight, MemDC, 0, 0, srcCopy);
  SelectObject(MemDC, OldBitmap);

  DeleteDC(MemDC);
end;

{ Repaint ------------------------------------------------------
    Repaint the button. Called whenever the button changes
    state.
  -------------------------------------------------------------- }
procedure Repaint;
var
  DC: HDC;
begin
  DC := GetDC(HWindow);
  Paint(DC);
  ReleaseDC(HWindow, DC);
end;

{ SetState -----------------------------------------------------
    Sets the value of a state bit.  If the word changes value
    the button is repainted.
  -------------------------------------------------------------- }
procedure SetState(AState: Word; Enable: Boolean);
var
  OldState, NewState: Word;
begin
  OldState := State;
  if Enable then NewState := OldState or AState
  else NewState := OldState and not AState;
  if NewState <> OldState then
  begin
    SetWord(ofState, NewState);
    Repaint;
  end;
end;

{ InMe ---------------------------------------------------------
    Returns true if the given point is in within the border of
    the button.
  -------------------------------------------------------------- }
function InMe(lPoint: Longint): Boolean;
var
  R: TRect;
  Point: TPoint absolute lPoint;
begin
  GetClientRect(HWindow, R);
  InflateRect(R, -bdBorderWidth, -bdBorderWidth);
  InMe := PtInRect(R, Point);
end;

{ ButtonPressed ------------------------------------------------
    Called when the button is pressed by either the keyboard or
    by the mouse.
  -------------------------------------------------------------- }
procedure ButtonPressed;
begin
  SetState(bsMouseDown + bsMouseUpDown + bsKeyDown, False);
  SendMessage(GetParent(HWindow), wm_Command, GetDlgCtrlID(HWindow),
	Longint(HWindow));
end;

{ LoadBits -----------------------------------------------------
    Load the bitmap for the button or the "NO BITMAP" version
    if it does not exist.
  -------------------------------------------------------------- }
procedure LoadBits(Wrd: Word; MapNumber: Word);
var
  MapBits: HBitmap;
begin
  MapBits := LoadBitmap(HInstance, pChar(MapNumber));
  if MapBits = 0 then
    if IsWorkshopWindow(HWindow) then
      MapBits := LoadBitmapRW(pChar(MapNumber))
    else
      MapBits := LoadBitmap(GetAppInstance, pChar(MapNumber));
  if MapBits = 0 then
    MapBits := LoadBitmap(HInstance, pChar(MapNumber - Get(gww_ID)));
  SetWord(Wrd, MapBits);
end;

begin
  BitButtonWinFn := 0;
  case Message of
    wm_Create:
      begin
	{ Detect EGA monitor }
	DC := GetDC(0);
	if (GetSystemMetrics(sm_CYScreen) < 480) or
	    (GetDeviceCaps(DC, numColors) < 16) then
	  BitsNumber := 2000 + Get(gww_ID)
	else
	  BitsNumber := 1000 + Get(gww_ID);
	ReleaseDC(0, DC);

	{ Load bitmaps from resource }
	LoadBits(ofUpBits, BitsNumber);
	LoadBits(ofDownBits, BitsNumber + 2000);
	LoadBits(ofFocUpBits, BitsNumber + 4000);

	{ Adjust size of button to size of bitmap }
	GetObject(DownBits, SizeOf(Bitmap), @Bitmap);
	GetWindowRect(HWindow, Rect);
	Pt.X := Rect.Left;
	Pt.Y := Rect.Top;
	ScreenToClient(PCreateStruct (lParam)^.hwndParent, Pt);
	  MoveWindow(HWindow, Pt.X, Pt.Y,
	  Bitmap.bmWidth + bdBorderWidth * 2,
	  Bitmap.bmHeight + bdBorderWidth * 2, False);

	{ Intialize button state }
	with PCreateStruct(lParam)^ do
	begin
	  if style and $1F = bs_DefPushButton then
		SetState(bsDefault, True);
	  if style and ws_Disabled <> 0 then
		SetState(bsDisabled, True);
	end;
	  end;
    wm_NCDestroy:
      begin
	{ Destroy all saved bitmaps before the button is destroyed }
	BitButtonWinFn := DefWindowProc(HWindow, Message, wParam, lParam);
	DeleteObject(UpBits);
	DeleteObject(DownBits);
	DeleteObject(FocUpBits);
      end;
    wm_Paint:
      begin
	BeginPaint(HWindow, PS);
	Paint(PS.hDC);
	EndPaint(HWindow, PS);
      end;
    wm_EraseBkGnd:
      begin
	{ Squelch the painting of the background to eliminate flicker }
      end;
    wm_Enable:
      SetState(bsDisabled, wParam <> 0);
    wm_SetFocus:
      SetState(bsFocus, True);
    wm_KillFocus:
      SetState(bsFocus or bsKeyDown or bsMouseDown or bsMouseUpDown, False);
    wm_KeyDown:
      if (wParam = $20) and not GetState(bsKeyDown) and
	  not GetState(bsMouseDown) then
	SetState(bsKeyDown, True);
    wm_KeyUp:
      if (wParam = $20) and GetState(bsKeyDown) then
        ButtonPressed;
    wm_LButtonDblClk, wm_LButtonDown:
      if InMe(lParam) and not GetState(bsKeyDown) then
      begin
	if GetFocus <> HWindow then SetFocus(HWindow);
	SetState(bsMouseDown, True);
	SetCapture(HWindow);
      end;
    wm_MouseMove:
      if GetState(bsMouseDown) then
	SetState(bsMouseUpDown, not InMe(lParam));
    wm_LButtonUp:
      if GetState(bsMouseDown) then
      begin
	ReleaseCapture;
	if not GetState(bsMouseUpDown) then ButtonPressed
	else SetState(bsMouseDown + bsMouseUpDown, False);
      end;

    { *** Handling the rest of these messages are what, at least for
          the dialog manager, makes a push button a push button.  ***}
    wm_GetDlgCode:
      { Sent by the dialog manager to determine the control kind of
	a child window.  Returning dlgc_DefPushButton or
	dlgc_UndefPushButton causes the dialog manager to treat the
	control like a button, sending the bm_SetStyle message to
	move the default button style to the currenly focused button.

        The dlgc_Button constant is not documented by Microsoft
        (however, it is documented for OS/2 PM, and appears to work
        the same). If this constant is or'd in, the windows dialog
        manager will take care of all accelerator key processing,
        sending bm_SetState and bm_SetStyle messages when an
        acclerator key is pressed. There is a side effect to using
        the message, however, the dialog manager messes with the word
        at offset 0 from the user Window words. }

      if GetState(bsDefault) then
	BitButtonWinFn:= dlgc_DefPushButton or dlgc_Button
      else
	BitButtonWinFn := dlgc_UndefPushButton or dlgc_Button;
    bm_GetState:
      BitButtonWinFn := Integer(GetState(bsKeyDown));
    bm_SetState:
      SetState(bsKeyDown, wParam <> 0);
    bm_SetStyle:
      { Sent by the dialog manager when the button receives or looses
	focus and is not the default button, or when another button
	receives the focus and this button is the default button. }
      SetState(bsDefault, wParam = bs_DefPushButton);
  else
    BitButtonWinFn := DefWindowProc(HWindow, Message, wParam, lParam);
  end;
end;

{ ==============================================================
  Custom contol interface routines.
  ============================================================== }

{ BitBtnInfo ---------------------------------------------------
   Return the information about the capabilities of the
   bit button class.
  -------------------------------------------------------------- }
function BitBtnInfo: THandle; export;
var
  hInfo: THandle;
  Info: PRWCtlInfo;
begin
  hInfo := GlobalAlloc(gmem_Share or gmem_ZeroInit,
    SizeOf(TRWCtlInfo));
  if hInfo <> 0 then
  begin
    Info := GlobalLock(hInfo);
    with Info^ do
    begin
      wVersion := $100;         { Version 1.00 }
      wCtlTypes := 2;           { 2 types }
      StrCopy(szClass, 'BitButton');
      StrCopy(szTitle, 'Button');

      { Normal (Un-default) push button type }
      with ctType[0] do
      begin
	wWidth := 63 or $8000;
	wHeight := 39 or $8000;
	StrCopy(szDescr, 'Push Button');
	dwStyle := bs_PushButton or ws_TabStop;
	hToolBit := LoadBitmap(HInstance, MakeIntResource(btUndefBits));
	hDropCurs := LoadCursor(HInstance, MakeIntResource(crUndefCurs));
      end;

      { Default push button type }
      with ctType[1] do
      begin
	wWidth := 63 or $8000;
	wHeight := 39 or $8000;
	StrCopy(szDescr, 'Default Push Button');
	dwStyle := bs_DefPushButton or ws_TabStop;
	hToolBit := LoadBitmap(HInstance, MakeIntResource(btDefBits));
	hDropCurs := LoadCursor(HInstance, MakeIntResource(crDefCurs));
      end;
    end;
    GlobalUnlock(hInfo);
  end;
  BitBtnInfo := hInfo;
end;

type
  PParamRec = ^TParamRec;
  TParamRec = record
    CtlStyle: THandle;
    IdToStr: TIdToStr;
    StrToId: TStrToId;
  end;

{ BitBtnStyleDlg -----------------------------------------------
    Style dialog's dialog hook.  Used by the dialog and called
    when the control is double-clicked inside the dialog
    editor.
  -------------------------------------------------------------- }
function BitBtnStyleDlg(HWindow: HWnd; Message: Word; wParam: Word;
  lParam: Longint): Longint; export;
const
  Prop = 'Prop';
var
  hRec: THandle;
  Rec: PParamRec;
  Style: PCtlStyle;
  S: array[0..256] of Char;
  Radio: Integer;
begin
  case Message of
    wm_InitDialog:
      begin
	hRec := LoWord(lParam);
	Rec := GlobalLock(hRec);
	Style := GlobalLock(Rec^.CtlStyle);
	SetProp(HWindow, Prop, hRec);
	with Rec^, Style^ do
	begin
	  { Set caption }
	  SetDlgItemText(HWindow, idCaption, szTitle);

	  { Set control id }
	  IdToStr(wId, S, SizeOf(S));
	  SetDlgItemText(HWindow, idControlId, S);

	  { Set type radio buttons }
	  if dwStyle and $F = bs_DefPushButton then
	    Radio := idDefaultButton
	  else
            Radio := idPushButton;
	  CheckRadioButton(HWindow, idDefaultButton, idPushButton,
	    Radio);

	  { Initialize Tab Stop check box }
	  CheckDlgButton(HWindow, idTabStop,
	    Integer(dwStyle and ws_TabStop <> 0));

	  { Initialize Disabled check box }
	  CheckDlgButton(HWindow, idDisabled,
	    Integer(dwStyle and ws_Disabled <> 0));

	  { Initialize Group check box }
	  CheckDlgButton(HWindow, idGroup,
	    Integer(dwStyle and ws_Group <> 0));
	end;
	GlobalUnlock(Rec^.CtlStyle);
	GlobalUnlock(hRec);
      end;
    wm_Command:
      case wParam of
	idCancel:
	  EndDialog(HWindow, 0);
	idOk:
	  begin
	    hRec := GetProp(HWindow, Prop);
	    Rec := GlobalLock(hRec);
	    Style := GlobalLock(Rec^.CtlStyle);
	    with Rec^, Style^ do
	    begin
	      { Get caption }
	      GetDlgItemText(HWindow, idCaption, szTitle, SizeOf(szTitle));

	      { Get control id }
	      GetDlgItemText(HWindow, idControlId, S, SizeOf(S));
	      wId := StrToId(S);

	      { Get button type }
	      if IsDlgButtonChecked(HWindow, idDefaultButton) <> 0 then
		dwStyle := bs_DefPushButton
	      else
                dwStyle := bs_PushButton;

	      { Get tab stop }
	      if IsDlgButtonChecked(HWindow, idTabStop) <> 0 then
		dwStyle := dwStyle or ws_TabStop;

	      { Get disabled }
	      if IsDlgButtonChecked(HWindow, idDisabled) <> 0 then
		dwStyle := dwStyle or ws_Disabled;

	      { Get group }
	      if IsDlgButtonChecked(HWindow, idGroup) <> 0 then
		dwStyle := dwStyle or ws_Group;
	    end;
	    GlobalUnlock(Rec^.CtlStyle);
	    GlobalUnlock(hRec);
	    EndDialog(HWindow, 1);
	  end;
      else
	BitBtnStyleDlg := 0;
      end;
    wm_Destroy:
      RemoveProp(HWindow, Prop);
  else
    BitBtnStyleDlg := 0;
  end;
end;

{ BitBtnStyle --------------------------------------------------
    The function will bring up a dialog box to modify the style
    of the button.  Called when the button is double-clicked in
    the dialog editor.
  -------------------------------------------------------------- }
function BitBtnStyle(hWindow: HWnd; CtlStyle: THandle;
  StrToId: TStrToId; IdToStr: TIdToStr): Bool; export;
var
  hRec: THandle;
  Rec: PParamRec;
  hFocus: HWnd;
begin
  BitBtnStyle := False;
  hRec := GlobalAlloc(gmem_Share, SizeOf(TParamRec));
  if hRec <> 0 then
  begin
    Rec := GlobalLock(hRec);
    Rec^.IdToStr := IdToStr;
    Rec^.StrToId := StrToId;
    Rec^.CtlStyle := CtlStyle;
    GlobalUnlock(hRec);

    hFocus := GetFocus;
    BitBtnStyle := Bool(DialogBoxParam(HInstance,
      MakeIntResource(idButtonStyle), HWindow, @BitBtnStyleDlg,
      hRec));
    if hFocus <> 0 then SetFocus(hFocus);
    GlobalFree(hRec);
  end;
end;

{ BitBtnFlags --------------------------------------------------
    Called to decompose the style double word into the .RC
    script expression that it represents.  This only needs to
    decompose the style bits added to the style double word,
    it need not decompose the, for example, the ws_XXX bits.
    The expression returned must be a valid .RC expression
    (i.e. C syntax, case sensitive).
  -------------------------------------------------------------- }
function BitBtnFlags(Style: LongInt; Buff: PChar;
  BuffLength: Word): Word; export;
begin
  if Style and $F = bs_DefPushButton then
    StrLCopy(Buff, 'BS_DEFPUSHBUTTON', BuffLength)
  else StrLCopy(Buff, 'BS_PUSHBUTTON', BuffLength);
end;

{ ListClasses --------------------------------------------------
    Called by Resource Workshop retrieve the information
    necessary to edit the custom controls contain in this DLL.
    This is an alternative to the Microsoft xxxStyle convention.
  -------------------------------------------------------------- }
function ListClasses(szAppName: PChar; wVersion: Word;
  fnLoad: TLoad; fnEdit: TEdit): THandle; export;
var
  hClasses: THandle;
  Classes: PCtlClassList;
begin
  LoadResRW := fnLoad;
  hClasses := GlobalAlloc(gmem_Share or gmem_ZeroInit,
    SizeOf(Integer) + SizeOf(TRWCtlClass));
  if hClasses <> 0 then
  begin
    Classes := GlobalLock(hClasses);
    with Classes^ do
    begin
      nClasses := 1;
      with Classes[0] do
      begin
	fnInfo  := BitBtnInfo;
	fnStyle := BitBtnStyle;
	fnFlags := BitBtnFlags;
      end;
    end;
    GlobalUnlock(hClasses);
  end;
  ListClasses := hClasses;
end;

exports
  ListClasses,
  BitButtonWinFn;

var
  Class: TWndClass;

begin
  with Class do
  begin
    lpszClassName := 'BitButton';
    hCursor       := LoadCursor(0, idc_Arrow);
    lpszMenuName  := nil;
    style         := cs_HRedraw or cs_VRedraw or cs_DblClks or cs_GlobalClass;
    lpfnWndProc   := TFarProc(@BitButtonWinFn);
    hInstance     := System.hInstance;
    hIcon         := 0;
    cbWndExtra    := ofSize;
    cbClsExtra    := 0;
    hbrBackground := 0;
  end;
  RegisterClass(Class);
end.
