{************************************************}
{                                                }
{   Demo program                                 }
{   Copyright (c) 1991 by Borland International  }
{                                                }
{************************************************}

program BScrlApp;

{$R BSCRLAPP.RES}

uses WinTypes, WinProcs, WinDos, OWindows, OMemory, OStdDlgs, Strings;

const
  bsa_Name =  'BitmapScroll';

type

{ TBitScrollApp, a TApplication descendant }

  TBitScrollApp = object(TApplication)
    procedure InitMainWindow; virtual;
  end;

{ TBitScrollWindow, a TWindow descendant }

  PScrollWindow = ^TBitScrollWindow;
  TBitScrollWindow = object(TWindow)
    FileName: array[0..fsPathName] of Char;
    BitmapHandle: HBitmap;
    IconizedBits: HBitmap;
    IconImageValid: Boolean;
    PixelHeight, PixelWidth: Word;
    Mode: Longint;
    constructor Init(ATitle: PChar);
    destructor Done; virtual;
    function GetClassName : PChar; virtual;
    procedure GetWindowClass(var WndClass: TWndClass); virtual;
    procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
    procedure CMFileOpen(var Msg: TMessage); virtual cm_First + cm_FileOpen;
    procedure WMSize(var Msg: TMessage); virtual wm_First + wm_Size;
    procedure AdjustScroller;
    function LoadBitmapFile(Name: PChar): Boolean;
    function OpenDIB(var TheFile: File): Boolean;
    procedure GetBitmapData(var TheFile: File;
      BitsHandle: THandle; BitsByteSize: Longint);
  end;

{ __ahIncr, ordinal 114, is a 'magic' function. Defining this
  function causes Windows to patch the value into the passed
  reference.  This makes it a type of global variable. To use
  the value of AHIncr, use Ofs(AHIncr). }

procedure AHIncr; far; external 'KERNEL' index 114;

{ Construct the TBitScrollApp's MainWindow of type TBitScrollWindow }

procedure TBitScrollApp.InitMainWindow;
begin
  MainWindow := New(PScrollWindow, Init(bsa_name));
end;

{ Constructor for a TBitScrollWindow, sets scroll styles and constructs
  the Scroller object.  Also sets the Mode based on whether the display
  is monochrome (two-color) or polychrome. }

constructor TBitScrollWindow.Init(ATitle: PChar);
var
  DCHandle: HDC;
begin
  TWindow.Init(nil, ATitle);
  Attr.Style := Attr.Style or ws_VScroll or ws_HScroll;
  Attr.Menu := LoadMenu(HInstance, bsa_Name);
  BitmapHandle := 0;
  IconImageValid := False;
  Scroller := New(PScroller, Init(@Self, 1, 1, 200, 200));
  DCHandle := CreateDC('Display', nil, nil, nil);
  IconizedBits := CreateCompatibleBitmap(DCHandle, 64, 64);
  if GetDeviceCaps(DCHandle, numColors) < 3 then Mode := notSrcCopy
  else Mode := srcCopy;
  DeleteDC(DCHandle);
end;

{ Change the class name to the application name. }

function TBitScrollWindow.GetClassName : PChar;
begin
  GetClassName := bsa_Name;
end;

{ Allow the iconic picture to be drawn from the client area. }

procedure TBitScrollWindow.GetWindowClass(var WndClass: TWndClass);
begin
  TWindow.GetWindowClass(WndClass);
  WndClass.hIcon := 0; { Client area will be painted by the app. }
end;

destructor TBitScrollWindow.Done;
begin
  if BitmapHandle <> 0 then DeleteObject(BitmapHandle);
  DeleteObject(IconizedBits);
  TWindow.Done;
end;

{ If the the 'Open...' menu item is selected, then, using
  the current TFileDlgRec we prompt the user for a new bitmap
  file.  If the user selects one and it is one that we can
  read, we display it in the window and change the window's
  caption to reflect the new bitmap file.  It should be noted
  that we save the old TFileDlgRec just in case we are unable
  to display the bitmap.  This allows us to restore the old
  search criteria. }

procedure TBitScrollWindow.CMFileOpen(var Msg: TMessage);
var
  TempName: array[0..fsPathName] of Char;
  CaptionBuffer: array [0..fsPathName+12{bsa_Name} +2{': '} +1{#0}] of Char;
begin
  if Application^.ExecDialog(New(PFileDialog,
    Init(@Self, PChar(sd_FileOpen), StrCopy(TempName, '*.bmp')))) = id_Ok then
    if LoadBitmapFile(TempName) then
    begin
      StrCopy(FileName, TempName);
      StrCopy(CaptionBuffer, bsa_Name);
      StrCat(CaptionBuffer, ': ');
      StrCat(CaptionBuffer, AnsiLower(FileName));
      SetWindowText(HWindow, CaptionBuffer);
    end;
end;

{ Adjust the Scroller range so that the the origin is the
  upper-most scrollable point and the corner is the
  bottom-most. }

procedure TBitScrollWindow.AdjustScroller;
var
  ClientRect: TRect;
begin
  GetClientRect(HWindow, ClientRect);
  with ClientRect do
    Scroller^.SetRange(PixelWidth - (right - left),
      PixelHeight - (bottom - top));
  Scroller^.ScrollTo(0, 0);
  InvalidateRect(HWindow, nil, True);
end;

{ Reset scroller range. }

procedure TBitScrollWindow.WMSize(var Msg: TMessage);
var
  DC, MemDC1, MemDC2: HDC;
  OldBitmap1, OldBitmap2: HBitmap;
  OldCursor: HCursor;
begin
  TWindow.WMSize(Msg);
  Scroller^.AutoOrg := not (Msg.wParam = sizeIconic);
  if not (Msg.WParam = sizeIconic) then AdjustScroller
  else if not IconImageValid and (BitmapHandle <> 0) then
  begin
    DC := GetDC(HWindow);
    MemDC1 := CreateCompatibleDC(DC);
    MemDC2 := CreateCompatibleDC(DC);
    ReleaseDC(HWindow, DC);
    OldBitmap1 := SelectObject(MemDC1, IconizedBits);
    OldBitmap2 := SelectObject(MemDC2, BitmapHandle);
    OldCursor := SetCursor(LoadCursor(0, idc_Wait));
    StretchBlt(MemDC1, 0, 0, Msg.lParamLo, Msg.lParamHi, MemDC2,
      0, 0, PixelWidth, PixelHeight, SrcCopy);
    SetCursor(OldCursor);
    SelectObject(MemDC1, OldBitmap1);
    SelectObject(MemDC2, OldBitmap2);
    DeleteDC(MemDC1);
    DeleteDC(MemDC2);
    IconImageValid := True;
  end;
end;

{ Copys the bitmap bit data from the file into memory. Since
  copying cannot cross a segment (64K) boundary, we are forced
  to do segment arithmetic to compute the next segment.  Created
  a LongType type to simplify the process. }

procedure TBitScrollWindow.GetBitmapData(var TheFile: File;
  BitsHandle: THandle; BitsByteSize: Longint);
type
  LongType = record
    case Word of
      0: (Ptr: Pointer);
      1: (Long: Longint);
      2: (Lo: Word;
	  Hi: Word);
  end;
var
  Count: Longint;
  Start, ToAddr, Bits: LongType;
begin
  Start.Long := 0;
  Bits.Ptr := GlobalLock(BitsHandle);
  Count := BitsByteSize - Start.Long;
  while Count > 0 do
  begin
    ToAddr.Hi := Bits.Hi + (Start.Hi * Ofs(AHIncr));
    ToAddr.Lo := Start.Lo;
    if Count > $4000 then Count := $4000;
    BlockRead(TheFile, ToAddr.Ptr^, Count);
    Start.Long := Start.Long + Count;
    Count := BitsByteSize - Start.Long;
  end;
  GlobalUnlock(BitsHandle);
end;

{ Attempt to open a Windows 3.0 device independent bitmap. }

function TBitScrollWindow.OpenDIB(var TheFile: File): Boolean;
var
  bitCount: Word;
  size: Word;
  longWidth: Longint;
  DCHandle: HDC;
  BitsPtr: Pointer;
  BitmapInfo: PBitmapInfo;
  BitsHandle, NewBitmapHandle: THandle;
  NewPixelWidth, NewPixelHeight: Word;
begin
  OpenDIB := True;
  Seek(TheFile, 28);
  BlockRead(TheFile, bitCount, SizeOf(bitCount));
  if bitCount <= 8 then
  begin
    size := SizeOf(TBitmapInfoHeader) + ((1 shl bitCount) * SizeOf(TRGBQuad));
    BitmapInfo := MemAlloc(size);
    Seek(TheFile, SizeOf(TBitmapFileHeader));
    BlockRead(TheFile, BitmapInfo^, size);
    NewPixelWidth := BitmapInfo^.bmiHeader.biWidth;
    NewPixelHeight := BitmapInfo^.bmiHeader.biHeight;
    longWidth := (((NewPixelWidth * bitCount) + 31) div 32) * 4;
    BitmapInfo^.bmiHeader.biSizeImage := longWidth * NewPixelHeight;
    GlobalCompact(-1);
    BitsHandle := GlobalAlloc(gmem_Moveable or gmem_Zeroinit,
      BitmapInfo^.bmiHeader.biSizeImage);
    GetBitmapData(TheFile, BitsHandle, BitmapInfo^.bmiHeader.biSizeImage);
    DCHandle := CreateDC('Display', nil, nil, nil);
    BitsPtr := GlobalLock(BitsHandle);
    NewBitmapHandle :=
      CreateDIBitmap(DCHandle, BitmapInfo^.bmiHeader, cbm_Init, BitsPtr,
      BitmapInfo^, 0);
    DeleteDC(DCHandle);
    GlobalUnlock(BitsHandle);
    GlobalFree(BitsHandle);
    FreeMem(BitmapInfo, size);
    if NewBitmapHandle <> 0 then
    begin
      if BitmapHandle <> 0 then DeleteObject(BitmapHandle);
      BitmapHandle := NewBitmapHandle;
      PixelWidth := NewPixelWidth;
      PixelHeight := NewPixelHeight;
    end
    else
      OpenDIB := False;
  end
  else
    OpenDIB := False;
end;

{ Test if the passed file is a Windows 3.0 DI bitmap and if so read it.
  Report errors if unable to do so. Adjust the Scroller to the new
  bitmap dimensions. }

function TBitScrollWindow.LoadBitmapFile(Name: PChar): Boolean;
var
  TheFile: File;
  TestWin30Bitmap: Longint;
  ErrorMsg: PChar;
  OldCursor: HCursor;
begin
  ErrorMsg := nil;
  OldCursor := SetCursor(LoadCursor(0, idc_Wait));
  Assign(TheFile, Name);
  {$I-}
  Reset(TheFile, 1);
  {$I+}
  if IOResult = 0 then
  begin
    Seek(TheFile, 14);
    BlockRead(TheFile, TestWin30Bitmap, SizeOf(TestWin30Bitmap));
    if TestWin30Bitmap = 40 then
      if OpenDIB(TheFile) then
      begin
	AdjustScroller;
	IconImageValid := False;
      end
      else ErrorMsg := 'Unable to create Windows 3.0 bitmap from file'
    else
      ErrorMsg := 'Not a Windows 3.0 bitmap file';
    Close(TheFile);
  end
  else
    ErrorMsg := 'Cannot open bitmap file';
  SetCursor(OldCursor);
  if ErrorMsg = nil then LoadBitmapFile := True else
  begin
    MessageBox(HWindow, ErrorMsg, bsa_Name, mb_Ok);
    LoadBitmapFile := False;
  end;
end;

{ Responds to an incoming "paint" message by redrawing the bitmap.  (The
  Scroller's BeginView method, which sets the viewport origin relative
  to the present scroll position, has already been called. )  }

procedure TBitScrollWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
var
  MemoryDC: HDC;
  OldBitmapHandle: THandle;
begin
  if BitmapHandle <> 0 then
  begin
    MemoryDC := CreateCompatibleDC(PaintDC);
    if IsIconic(HWindow) then
      OldBitmapHandle := SelectObject(MemoryDC, IconizedBits)
    else
    begin
      OldBitmapHandle := SelectObject(MemoryDC, BitmapHandle);
      if Mode = srcCopy then
      begin
	SetBkColor(PaintDC, GetNearestColor(PaintDC, $800000));
	SetTextColor(PaintDC, $FFFFFF);
      end;
    end;
    BitBlt(PaintDC, 0, 0, PixelWidth, PixelHeight, MemoryDC, 0, 0,
      Mode);
    SelectObject(MemoryDC, OldBitmapHandle);
    DeleteDC(MemoryDC);
  end;
end;

{ Declare a variable of type TBitScrollApp }

var
  ScrollApp: TBitScrollApp;

{ Run the BitScrollApp }

begin
  ScrollApp.Init(bsa_Name);
  ScrollApp.Run;
  ScrollApp.Done;
end.
