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

program GDIDemo;

uses WinProcs, WinTypes, Objects, OWindows, ODialogs, Strings;

{$R GDIDEMO.RES}

{ Menu bar constants }
const
  MenuID              = 100; { Resource ID of the menu }
  QuitID              = 100; { File->Quit ID }
  MoveToLineToDemoID  = 200; { Demo->MoveToDemo ID }
  FontDemoID          = 202; { Demo->Font Demo ID }
  BitBltDemoID        = 203; { Demo->BitBlt Demo ID }
  ArtyDemoID          = 204; { Demo->Arty Demo ID }

{ BitBlt demo constants }
const
  BackgroundID        = 100; { Bitmap ID of background bitmap }
  ShipID              = 101; { Bitmap ID of Ship Bitmap }
  MonoShipID          = 102; { Bitmap ID of Monochrome mask of ship }
  BitmapSize          = 72;  { Size of Ship bitmap }

{ Font demo constants }
const
  MaxNumFonts =  20; { Maximum number of fonts to be displayed in FontDemo }

{ MoveToLineTo demo constants }
const
  MaxPoints   =  15; { Number of points to be drawn in MoveToLineToDemo }

{ Arty demo constants }
const
   MaxLineCount  = 100;
   MaxIconicLineCount = 5;
   MaxColorDuration = 10;

function Min(X, Y: Integer): Integer;
begin
  if X > Y then Min := Y else Min := X;
end;

{ TBaseDemoWindow -------------------------------------------------- }

type
  PBaseDemoWindow = ^TBaseDemoWindow;
  TBaseDemoWindow = object(TWindow)
    procedure TimerTick; virtual;
  end;

{ Trivial method that gets called whenever application receives a
  WM_Timer.  Descendants will override this procedure if they need
  timer messages.}
procedure TBaseDemoWindow.TimerTick;
begin
end;

{ TNoIconWindow --------------------------------------------------- }

type
  PNoIconWindow = ^TNoIconWindow;
  TNoIconWindow = object(TBaseDemoWindow)
    procedure GetWindowClass(var AWndClass: TWndClass);  virtual;
    function GetClassName: PChar;  virtual;
  end;

{ Alter the default window class record to make this window have
  a black background and no "white box" icon.  }
procedure TNoIconWindow.GetWindowClass(var AWndClass: TWndClass);
begin
  TBaseDemoWindow.GetWindowClass(AWndClass);
  AWndClass.hbrBackground := GetStockObject(Black_Brush);
  AWndClass.hIcon := 0;
end;

{ No need to call the ancestor's method here, since we want to
  provide an entirely new window class name. }
function TNoIconWindow.GetClassName: PChar;
begin
  GetClassName := 'NoIconWindow';
end;

{ TMoveToLineToWindow --------------------------------------------- }

type
  TRPoint = record
    X, Y: Real;
  end;

type
  PMoveToLineToWindow = ^TMoveToLineToWindow;
  TMoveToLineToWindow = object(TBaseDemoWindow)
    Points: array[0..MaxPoints] of TRPoint;
    constructor Init(AParent: PWindowsObject; ATitle: PChar);
    procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
  end;

constructor TMoveToLineToWindow.Init(AParent: PWindowsObject; ATitle: PChar);
var
  I: Integer;
  StepAngle: Integer;
  Radians: Real;
begin
  TBaseDemoWindow.Init(AParent, ATitle);
  StepAngle := 360 div MaxPoints;
  for I := 0 to MaxPoints - 1 do
  begin
    Radians := (StepAngle * I) * PI / 180;
    Points[I].x := Cos(Radians);
    Points[I].y := Sin(Radians);
  end;
end;

procedure TMoveToLinetoWindow.Paint(PaintDC: HDC;
  var PaintInfo: TPaintStruct);
var
  TheRect: TRect;
  I, J: Integer;
  CenterX,
  CenterY: Integer;
  Radius,
  StepAngle: Word;
  Radians: real;
begin
  GetClientRect(HWindow,TheRect);
  CenterX := TheRect.Right div 2;
  CenterY := TheRect.Bottom div 2;
  Radius := Min(CenterY, CenterX);
  Ellipse(PaintDC,CenterX - Radius, CenterY - Radius, CenterX + Radius,
    CenterY + Radius);
  for I := 0 to MaxPoints - 1 do
  begin
    for J := I + 1 to MaxPoints - 1 do
    begin
      MoveTo(PaintDC, CenterX + Round(Points[I].X * Radius),
	CenterY + Round(Points[I].Y * Radius));
      LineTo(PaintDC, CenterX + Round(Points[J].X * Radius),
	CenterY + Round(Points[J].Y * Radius));
    end;
  end;
end;

{ TFontWindow ------------------------------------------------------ }

type
  FontInfoRec = record
    Handle: HFont;  { Handle to logical font }
    Height: Byte;   { Height of logical font in pixels }
    Width: LongInt; { Width of name of the font in pixels }
    Name: array[0..lf_FaceSize-1] of char; { Name of this font }
  end;

const
  FontUsers: Integer = 0;
var
  FontInfo: array[0..MaxNumFonts] of FontInfoRec;
  NumFonts: Integer; { Number of system fonts available }
  TheDC: HDC;

type
  PFontWindow = ^TFontWindow;
  TFontWindow = object(TBaseDemoWindow)
    FontsHeight: LongInt;
    FontsWidth: LongInt;
    constructor Init(AParent: PWindowsObject; ATitle: PChar);
    procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
    procedure Destroy; virtual;
    procedure WMSize(var Msg: TMessage);
      virtual wm_First + wm_Size;
  end;

{ EnumerateFont is a call back function.  It receives information
  about system fonts.  It creates an example of each font by calling
  CreateFont when MaxNumFonts have been processed, 0 is returned
  notifying windows to stop sending information, otherwise 1 is
  returned telling windows to send more information if available }
function EnumerateFont(var LogFont: TLogFont; TextMetric: PTextMetric;
  FontType: Integer; Data: PChar): Integer; export;
var
  OldFont: HFont;
begin
  { Create the font described by LogFont }
  FontInfo[NumFonts].Handle := CreateFontIndirect(LogFont);
  with LogFont do
  begin
    { Save the height of the font for positioning when drawing in
      the window }
    FontInfo[NumFonts].Height := lfHeight;
    { Save the name of the font for drawing in the window }
    StrCopy(FontInfo[NumFonts].Name, lfFaceName);
    OldFont := SelectObject(TheDC, FontInfo[NumFonts].Handle);
    FontInfo[NumFonts].Width := Word(GetTextExtent(TheDC, lfFaceName,
      StrLen(lfFaceName)));
    SelectObject(TheDC, OldFont);
  end;
  Inc(NumFonts);
  if NumFonts > MaxNumFonts then
    EnumerateFont := 0 { Don't send any more information }
  else
    EnumerateFont := 1; { Send more information if available }
end;

{ Collect all of the system fonts }
procedure GetFontInfo;
var
  EnumProc: TFarProc;
begin
  if FontUsers = 0 then
  begin
    TheDC := GetDC(GetFocus);
    NumFonts := 0;
    { Create an instance of the call back function.  This allows
      our program to refer to an exported function.  Otherwise the
      Data segment will not be correct. }
    EnumProc := MakeProcInstance(@EnumerateFont, HInstance);
    { Gather information about all fonts that are allowable in our window (DC) }
    EnumFonts(TheDC, nil, EnumProc, nil);
    { Free the instance of our call back function }
    FreeProcInstance(EnumProc);
    ReleaseDC(GetFocus, TheDC);
  end;
  Inc(FontUsers);
end;

{ Release font information }
procedure ReleaseFontInfo;
var
  I: Integer;
begin
  Dec(FontUsers);
  if FontUsers = 0 then
    for I := 0 to NumFonts - 1 do
      DeleteObject(FontInfo[I].Handle);
end;

{ Initialize object and collect font information }
constructor TFontWindow.Init(AParent: PWindowsObject; ATitle: PChar);
var
  I: Integer;

function Max(I, J: LongInt): LongInt;
begin
  if I > J then Max := I else Max := J;
end;

begin
  TBaseDemoWindow.Init(AParent, ATitle);
  GetFontInfo;
  Attr.Style := Attr.Style or ws_VScroll or ws_HScroll;
  FontsHeight := 0;
  FontsWidth := 0;
  for I := 0 to NumFonts - 1 do
  begin
    Inc(FontsHeight, FontInfo[I].Height);
    FontsWidth := Max(FontsWidth, FontInfo[I].Width);
  end;
  Scroller := New(PScroller, Init(@Self, 1, 1, 0, 0));
end;

{ Draw each font name in it's font in the Display context.  Each
  line is incremented by the height of the font }
procedure TFontWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
var
  I: Integer;
  Position: Integer;
begin
  Position := 0;
  for I := 0 to NumFonts - 1 do
  begin
    SelectObject(PaintDC, FontInfo[I].Handle);
    TextOut(PaintDC, 10, Position, FontInfo[I].Name,
      StrLen(FontInfo[I].Name));
    Inc(Position, FontInfo[I].Height);
  end;
end;

procedure TFontWindow.Destroy;
var
  I: Integer;
begin
  TBaseDemoWindow.Destroy;
  ReleaseFontInfo;
end;

procedure TFontWindow.WMSize(var Msg: TMessage);
begin
  TWindow.WMSize(Msg);
  if Scroller <> nil then
    Scroller^.SetRange(FontsWidth - Msg.lParamLo + 10,
      FontsHeight - Msg.lParamHi);
end;

{ TBitBltWindow ---------------------------------------------------- }

type
  PBitBltWindow = ^TBitBltWindow;
  TBitBltWindow = object(TNoIconWindow)
    WindowSize: TPoint;
    ScratchBitmap,
    StretchedBkgnd,
    Background,
    MonoShip,
    Ship: HBitmap;
    OldX, OldY,
    Delta,
    X, Y: Integer;
    CurClick: Integer;
    constructor Init(AParent: PWindowsObject; ATitle: PChar);
    destructor Done; virtual;
    procedure WMSize(var Message: TMessage); virtual WM_Size;
    procedure WMPaint(var Message: TMessage); virtual WM_Paint;
    procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
    procedure SetupWindow; virtual;
    procedure TimerTick; virtual;
    procedure CalculateNewXY;
  end;

{ Initialize the bitblt demo window and allocate bitmaps }
constructor TBitBltWindow.Init(AParent: PWindowsObject; ATitle: PChar);
begin
  TNoIconWindow.Init(AParent, ATitle);
  Background := LoadBitmap(HInstance, MakeIntResource(BackgroundID));
  Ship := LoadBitmap(HInstance, MakeIntResource(ShipID));
  MonoShip := LoadBitmap(HInstance, MakeIntResource(MonoShipID));
  ScratchBitmap := 0;
  StretchedBkgnd := 0;
  OldX := 0;
  OldY := 0;
  X := 0;
  Y := 0;
  Delta := 5;
  CurClick := 1;
end;

{ Dispose of all used resources }
destructor TBitBltWindow.Done;
begin
  DeleteObject(Background);
  DeleteObject(Ship);
  DeleteObject(MonoShip);
  if ScratchBitmap <> 0 then DeleteObject(ScratchBitmap);
  if StretchedBkgnd <> 0 then DeleteObject(StretchedBkgnd);
  TNoIconWindow.Done;
end;

{ Allocate scratch bitmaps }
procedure TBitBltWindow.SetupWindow;
var
  HandleDC: HDC;
begin
  TNoIconWindow.SetupWindow;
  HandleDC := GetDC(HWindow);
  ScratchBitmap := CreateCompatibleBitmap(HandleDC, 80, 80);
  StretchedBkgnd := CreateCompatibleBitmap(HandleDC, 1000, 1000);
  ReleaseDC(HWindow, HandleDC);
end;

{ Record the new size and stretch the background to it }
procedure TBitBltWindow.WMSize(var Message: TMessage);
var
  HandleDC, MemDC, StretchedDC: HDC;
  StretchObject, MemObject: THandle;
  PS: TPaintStruct;
  OldCur: HCursor;
begin
  TNoIconWindow.WMSize(Message);
  WindowSize.X := Message.LParamLo;
  WindowSize.Y := Message.LParamHi;

  HandleDC := GetDC(HWindow);

  { Create a stretched to fit background }
  StretchedDC := CreateCompatibleDC(HandleDC);
  MemDC := CreateCompatibleDC(HandleDC);
  StretchObject := SelectObject(StretchedDC, StretchedBkgnd);
  MemObject := SelectObject(MemDC, Background);
  OldCur := SetCursor(LoadCursor(0, idc_Wait));
  with WindowSize do
    StretchBlt(StretchedDC, 0, 0, X, Y, MemDC, 0, 0, 100, 100, SrcCopy);
  SetCursor(OldCur);
  SelectObject(StretchedDC, StretchObject);
  SelectObject(MemDC, MemObject);
  DeleteDC(MemDC);
  DeleteDC(StretchedDC);
  ReleaseDC(HWindow, HandleDC);
end;

{ Need to ensure that the Old copy of the ship gets redrawn with
  any paint messages. }
procedure TBitBltWindow.WMPaint(var Message: TMessage);
var
  Rect: TRect;
begin
  Rect.Top := OldY;
  Rect.Left := OldX;
  Rect.Bottom := OldY+BitmapSize;
  Rect.Right := OldX+BitmapSize;
  InvalidateRect(HWindow, @Rect, False);
  TNoIconWindow.WMPaint(Message);
end;

procedure TBitBltWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
var
  MemDC: HDC;
  MemObject: THandle;
begin
  MemDC := CreateCompatibleDC(PaintDC);
  MemObject := SelectObject(MemDC, StretchedBkgnd);
  with WindowSize do
    BitBlt(PaintDC, 0, 0, X, Y, MemDC, 0, 0, SrcCopy);
  SelectObject(MemDC, MemObject);
  DeleteDC(MemDC);
end;

{ TimerTick deletes the old position of the saucer and blt's a new one }
procedure TBitBltWindow.TimerTick;
const
  ClicksToSkip = 4;
var
  Bits, BackingStore, WindowDC: HDC;
  SavedBitsObject, SavedStoreObject: THandle;
  BX, BY, OX, OY, BH, BW: Integer;
begin
  { Make the saucer go slower then everyone else }
  if CurClick < ClicksToSkip then
  begin
    Inc(CurClick);
    Exit;
  end
  else CurClick := 1;

  TNoIconWindow.TimerTick;

  { Setup the DC's }
  WindowDC := GetDC(HWindow);
  Bits := CreateCompatibleDC(WindowDC);
  BackingStore := CreateCompatibleDC(WindowDC);

  CalculateNewXY;

  { Calulate the offsets into and dimentions of the backing store }
  BX := Min(X, OldX);
  BY := Min(Y, OldY);
  OX := Abs(X - BX);
  OY := Abs(Y - BY);
  BW := 72 + Abs(OldX - X);
  BH := 72 + Abs(OldY - Y);

  { Create an image into the backing store the will that, when blt into
    the window will both erase the old image and draw the new one. }
  SavedStoreObject := SelectObject(BackingStore, ScratchBitmap);
  SavedBitsObject := SelectObject(Bits, StretchedBkgnd);
  BitBlt(BackingStore, 0, 0, BW, BH, Bits, BX, BY, srcCopy);
  SelectObject(Bits, MonoShip);
  BitBlt(BackingStore, OX, OY, 72, 72, Bits, 0, 0, SrcAnd);
  SelectObject(Bits, Ship);
  BitBlt(BackingStore, OX, OY, 72, 72, Bits, 0, 0, SrcPaint);

  { Blt the backing store to the window }
  BitBlt(WindowDC, BX, BY, BW, BH, BackingStore, 0, 0, SrcCopy);

  { Clean up the DC's }
  SelectObject(Bits, SavedBitsObject);
  SelectObject(BackingStore, SavedStoreObject);
  DeleteDC(Bits);
  DeleteDC(BackingStore);
  ReleaseDC(HWindow, WindowDC);

  OldX := X;
  OldY := Y;
end;

procedure TBitBltWindow.CalculateNewXY;
begin
  if WindowSize.X < BitmapSize then Exit;  { Don't move if too small }
  if (X > WindowSize.X - BitmapSize) or (X < 0) then
  begin
    Delta := -Delta;
    if X > WindowSize.X - BitmapSize then
      X := WindowSize.X - BitmapSize - 5;
  end;
  X := X + Delta;
  Y := Y + Integer(Random(10)) - 5;
  if Y > WindowSize.Y - BitmapSize then Y := WindowSize.Y - BitmapSize
  else if Y < 0 then Y := 0;
end;

{ TArtyWindow ------------------------------------------------------ }

type
  TLineRec = record
    LX1,LY1: Integer;
    LX2,LY2: Integer;
    Color: Longint;
  end;

  PLineList = ^TLineList;
  TLineList = array[1..MaxLineCount] of TLineRec;

  PList = ^TList;
  TList = object(TObject)
    Line: PLineList;
    MaxLines,
    Xmax, Ymax,
    X1, Y1, X2, Y2,
    MaxDelta,
    ColorDuration,
    IncrementCount,
    DeltaX1, DeltaY1, DeltaX2, DeltaY2,
    CurrentLine: Integer;
    PenColor: Longint;
    Paused: Boolean;
    constructor Init(Max: Integer);
    destructor Done;  virtual;
    procedure AdjustX(var X, DeltaX: Integer);
    procedure AdjustY(var Y, DeltaY: Integer);
    procedure Draw(DC: HDC; a1, b1, a2, b2: Integer; lPenColor: Longint);
    procedure DrawLine(DC: HDC; Index: Integer);  virtual;
    procedure EraseLine(DC: HDC; Index: Integer); virtual;
    procedure Redraw(DC: HDC);
    procedure ResetLines;
    procedure ScaleTo(NewXmax, NewYmax: Integer);
    procedure SelectNewColor;
    procedure SelectNewDeltaValues;
    procedure LineTick(DC: HDC);
  end;

  PQuadList = ^TQuadList;
  TQuadList = object(TList)   { Quads draw 4 reflections of each line }
    procedure DrawLine(DC: HDC; Index: Integer);  virtual;
    procedure EraseLine(DC: HDC; Index: Integer);  virtual;
  end;

  PArtyWindow = ^TArtyWindow;
  TArtyWindow = object(TNoIconWindow)
    List,
    BigLineList,
    IconicLineList : PList;
    TextHeight: Integer;
    Iconized : Boolean;
    StaticControl: PStatic;
    constructor Init(aParent: PWindowsObject; aTitle: PChar);
    destructor Done;  virtual;
    procedure Paint(PaintDC: HDC; var PaintInfo: TPaintStruct); virtual;
    procedure WMLButtonDown(var Message: TMessage);
      virtual wm_First + wm_LButtonDown;
    procedure WMRButtonDown(var Message: TMessage);
      virtual wm_First + wm_RButtonDown;
    procedure WMSize(var Msg: TMessage);
      virtual wm_First + wm_Size;
    procedure TimerTick; virtual;
  end;

{ Initialize the list-of-lines object }
constructor TList.Init(Max: Integer);
begin
  TObject.Init;
  If Max > MaxLineCount then
    Max := MaxLineCount;

  { Don't change MaxLines!  It will be used to free memory in Done}
  MaxLines := Max;
  GetMem(Line, SizeOf(TLineRec) * MaxLines);
  CurrentLine := 1;
  Xmax := 0;
  Ymax := 0;
  ColorDuration := MaxColorDuration;
  IncrementCount := 0;
  MaxDelta := 10;
  PenColor := RGB(Random(256), Random(256), Random(256));
  Paused := False;
end;

destructor TList.Done;
begin
  FreeMem(Line, SizeOf(TLineRec) * MaxLines);
  TObject.Done;
end;

{ Keep X within range, and reverse Delta if necessary to do so }
procedure TList.AdjustX(var X, DeltaX: Integer);
var
  TestX: Integer;
begin
  TestX := X + DeltaX;
  if (TestX < 1) or (TestX > Xmax) then
  begin
    TestX := X;
    DeltaX := -DeltaX;
  end;
  X := TestX;
end;

{ Keep Y within range, and reverse Delta if necessary to do so }
procedure TList.AdjustY(var Y,DeltaY: Integer);
var
  TestY: Integer;
begin
  TestY := Y + DeltaY;
  if (TestY < 1) or (TestY > Ymax) then
  begin
    TestY := Y;
    DeltaY := -DeltaY;
  end;
  Y := TestY;
end;

{ Clear the array of lines }
procedure TList.ResetLines;
var
  StartX, StartY, I: Integer;
begin
  StartX := Xmax div 2;
  StartY := Ymax div 2;
  for I := 1 to MaxLines do
    with Line^[I] do
    begin
      LX1 := StartX; LX2 := StartX;
      LY1 := StartY; LY2 := StartY;
      Color := 0;
    end;
  X1 := StartX;
  X2 := StartX;
  Y1 := StartY;
  Y2 := StartY;
end;

{ Scale the old line coordinates to the new Xmax and Ymax coordinates.
  The new Xmax and new Ymax are passed in as parameters so we can
  calculate the scaling ratios. }
procedure TList.ScaleTo(NewXmax, NewYMax: Integer);
var
  I: Integer;
  RatioX, RatioY: Real;
begin
  if (Xmax = 0) or (Ymax = 0) then { at startup, Xmax and Ymax are zero }
  begin
    Xmax := NewXmax;
    Ymax := NewYmax;
    ResetLines;
  end
  else
  begin
    RatioX := NewXMax / Xmax;
    RatioY := NewYmax / Ymax;
    X1 := Trunc(X1 * RatioX);
    X2 := Trunc(X2 * RatioX);
    Y1 := Trunc(Y1 * RatioY);
    Y2 := Trunc(Y2 * RatioY);
    for I := 1 to MaxLines do
      with Line^[I] do
      begin
	LX1 := Trunc(LX1 * RatioX);
	LX2 := Trunc(LX2 * RatioX);
	LY1 := Trunc(LY1 * RatioY);
	LY2 := Trunc(LY2 * RatioY);
      end;
  end;
  Xmax := NewXmax;
  Ymax := NewYmax;
end;

{ The low-level Draw method of the object. }
procedure TList.Draw(DC: HDC; a1, b1, a2, b2: Integer; lPenColor: Longint);
var
  OldPen: HPen;
begin
  OldPen := SelectObject(DC, CreatePen(PS_SOLID, 1, lPenColor));
  MoveTo(DC, a1, b1);
  LineTo(DC, a2, b2);
  DeleteObject(SelectObject(DC, OldPen));
end;

{ The high-level Draw method of the object. }
procedure TList.DrawLine(DC: HDC; Index: Integer);
begin
  with Line^[Index] do
    Draw(DC, LX1, LY1, LX2, LY2, Color);
end;

{ The high-level draw which erases a line. }
procedure TList.EraseLine(DC: HDC; Index: Integer);
begin
  with Line^[Index] do
    Draw(DC, LX1, LY1, LX2, LY2, RGB(0, 0, 0));
end;

{ Redraw all the lines in the array. }
procedure TList.Redraw(DC: HDC);
var I: Integer;
begin
  for I := 1 to MaxLines do
    DrawLine(DC, I);
end;

{ Reset the color counter and pick a random color. }
procedure TList.SelectNewColor;
begin
  ColorDuration := MaxColorDuration;
  PenColor := RGB(Random(256), Random(256), Random(256));
end;

{ Pick random directional deltas and reset the delta counter. }
procedure TList.SelectNewDeltaValues;
begin
  DeltaX1 := Random(MaxDelta)-(MaxDelta Div 2);
  DeltaX2 := Random(MaxDelta)-(MaxDelta Div 2);
  DeltaY1 := Random(MaxDelta)-(MaxDelta Div 2);
  DeltaY2 := Random(MaxDelta)-(MaxDelta Div 2);
  IncrementCount := 2*(1+Random(10));
end;

{ Process the movement of one line. }
procedure TList.LineTick(DC: HDC);
begin
    EraseLine(DC, CurrentLine);
    if ColorDuration < 0 then SelectNewColor;
    if IncrementCount=0 then SelectNewDeltaValues;
    AdjustX(X1,DeltaX1); AdjustX(X2,DeltaX2);
    AdjustY(Y1,DeltaY1); AdjustY(Y2,DeltaY2);
    with Line^[CurrentLine] do
    begin
      LX1 := X1;  LX2 := X2;
      LY1 := Y1;  LY2 := Y2;
      Color := PenColor;
    end;
    DrawLine(DC, CurrentLine);
    Inc(CurrentLine);
    if CurrentLine > MaxLines then CurrentLine := 1;
    Dec(ColorDuration);
    Dec(IncrementCount);
end;

{ Draw the line and 3 reflections of it. }
procedure TQuadList.DrawLine(DC: HDC; Index: Integer);
begin
  with Line^[Index] do
  begin
    Draw(DC,LX1,LY1,LX2,LY2,Color);
    Draw(DC,Xmax-LX1,LY1,Xmax-LX2,LY2,Color);
    Draw(DC,LX1,Ymax-LY1,LX2,Ymax-LY2,Color);
    Draw(DC,Xmax-LX1,Ymax-LY1,Xmax-LX2,Ymax-LY2,Color);
  end;
end;

{ Erase the line and 3 reflections of it. }
procedure TQuadList.EraseLine(DC: HDC; Index: Integer);
begin
  with Line^[Index] do
  begin
    Draw(DC, LX1, LY1, LX2, LY2, RGB(0,0,0));
    Draw(DC, Xmax-LX1, LY1,Xmax-LX2, LY2, RGB(0,0,0));
    Draw(DC, LX1,Ymax-LY1, LX2, Ymax-LY2, RGB(0,0,0));
    Draw(DC, Xmax-LX1, Ymax-LY1, Xmax-LX2, Ymax-LY2, RGB(0,0,0));
  end;
end;

constructor TArtyWindow.Init(AParent: PWindowsObject; ATitle: PChar);
begin
  TNoIconWindow.Init(AParent, ATitle);
  StaticControl := New(PStatic,Init(@Self,100,
    'Press Left Button to pause, Right Button to Clear',10,10,10,10,0));
  Iconized := False;
  TextHeight := 20;

  { Initialize two line list objects:
      BigLineList is the 4-reflection artwork that is displayed in
	a full sized window.  Mouse clicks will pause or clear
	the display, and the line list will be scaled to the
	new window coordinates when the window is resized.
      IconicLineList is a smaller list implementing a single-line
	quark to display in the iconized window region.  Since
	mouse clicks are not sent to iconized windows, the icon
	cannout be paused or cleared, and since there is only one
	icon window size, scaling the lines to new coordinates
	has no visual effect.
    The List pointer will be toggled between the two line list
    objects: when the window is iconized, List will point to the
    IconicLineList object.  When the window is restored to full
    size, List will be made to point to the BigLineList object.
    This is so the window routines don't have to know which kind
    of list they're dealing with.  Keyword: polymorphism.   }

  BigLineList := New(PQuadList, Init(MaxLineCount));
  IconicLineList := New(PList, Init(MaxIconicLineCount));
  List := BigLineList;
end;

{ Dispose of the objects that this window object created.  There's
  no need to dispose the List pointer, since it will only point to
  one of these two objects which are being disposed by their
  primary pointers }
destructor TArtyWindow.Done;
begin
  TNoIconWindow.Done;
  Dispose(BigLineList, Done);
  Dispose(IconicLineList, Done);
end;

{ When the window is resized, scale the line list to fit the new
  window extent, or switch between full size and iconized window
  states.  }
procedure TArtyWindow.WMSize(var Msg: TMessage);
var
  NewXmax, NewYmax: Integer;
begin
  TNoIconWindow.WMSize(Msg);
  { Force Windows to repaint the entire window region }
  InvalidateRect(HWindow, nil, True);
  NewXmax := Msg.LParamLo;
  NewYmax := Msg.LParamHi;
  if IsIconic(HWindow) then
    if not Iconized then
    begin
      Iconized := True;
      List := IconicLineList;
    end
    else
  else
  begin
    if Iconized then
    begin
      Iconized := False;
      List := BigLineList;
    end;
    Dec(NewYmax, TextHeight);  { allow room for the text at the bottom }
  end;
  List^.ScaleTo(NewXmax, NewYmax);  { scale the lines in the list }
  MoveWindow(StaticControl^.HWindow, 0, NewYmax, NewXmax, TextHeight, True);
end;

{ Toggle the list object's Paused status.  Since the window will
  not receive mouse clicks when iconized, this will not pause the
  iconized lines display.  }
procedure TArtyWindow.WMLButtonDown(var Message: TMessage);
begin
  List^.Paused := not List^.Paused;
end;

{ Clear the line list when the user presses the right mouse
  button.  Same comments as above on iconized windows.  }
procedure TArtyWindow.WMRButtonDown(var Message: TMessage);
begin
  InvalidateRect(HWindow,nil,True);
  List^.ResetLines;
end;

{ When the window is resized, or some other window blots out part
  of our client area, redraw the entire line list.  The PaintDC
  is fetched before Paint is called and is released for us after
  Paint is finished. }
procedure TArtyWindow.Paint(PaintDC: HDC; var PaintInfo: TPaintStruct);
begin
  TNoIconWindow.Paint(PaintDC, PaintInfo);
  List^.Redraw(PaintDC);
end;

{ Fetch a device context, pass it to the line list object, then
  release the device context back to Windows.  }
procedure TArtyWindow.TimerTick;
var
  DC: HDC;
begin
  if not List^.Paused then
  begin
    DC := GetDC(HWindow);
    List^.LineTick(DC);
    ReleaseDC(HWindow, DC);
  end;
end;

{ TGDIDemoWindow --------------------------------------------------- }

type
  PGDIDemoWindow = ^TGDIDemoWindow;
  TGDIDemoWindow = object(TMDIWindow)
    procedure SetupWindow; virtual;
    procedure MoveToLineToDemo(var Msg: TMessage);
      virtual cm_First + MoveToLineToDemoID;
    procedure FontDemo(var Msg: TMessage);
      virtual cm_First + FontDemoID;
    procedure BitBltDemo(var Msg: TMessage);
      virtual cm_First + BitBltDemoID;
    procedure ArtyDemo(var Msg: TMessage);
      virtual cm_First + ArtyDemoID;
    procedure Quit(var Msg: TMessage);
      virtual cm_First + QuitID;
    procedure WMTimer(var Msg: TMessage);
      virtual wm_First + wm_Timer;
    procedure WMDestroy(var Msg: TMessage);
      virtual wm_First + wm_Destroy;
  end;

procedure TGDIDemoWindow.SetupWindow;
var
  Result: Integer;
begin
  TMDIWindow.SetupWindow;
  Result := IDRetry;
  while (SetTimer(hWIndow, 0, 50, nil) = 0) and (Result = IDRetry) do
    Result := MessageBox(GetFocus,'Could not Create Timer', 'GDIDemo',
      mb_RetryCancel);
  if Result = IDCancel then PostQuitMessage(0);
end;

procedure TGDIDemoWindow.MoveToLineToDemo(var Msg: TMessage);
begin
  Application^.MakeWindow(New(PMoveToLineToWindow, Init(@Self,
    'MoveTo/LineTo Window')));
end;

procedure TGDIDemoWindow.FontDemo(var Msg: TMessage);
begin
  Application^.MakeWindow(New(PFontWindow, Init(@Self, 'Font Window')));
end;

procedure TGDIDemoWindow.BitBltDemo(var Msg: TMessage);
begin
  Application^.MakeWindow(New(PBitBltWindow, Init(@Self, 'BitBlt Window')));
end;

procedure TGDIDemoWindow.ArtyDemo(var Msg: TMessage);
begin
  Application^.MakeWindow(New(PArtyWindow, Init(@Self, 'Arty Window')));
end;

procedure TGDIDemoWindow.Quit(var Msg: TMessage);
begin
  CloseWindow;
end;

{ In response to WMTimer messages, each MDI child window's TimerTick
  Method is called. }
procedure TGDIDemoWindow.WMTimer(var Msg: TMessage);

  procedure ChildTimers(PChildWindow: PBaseDemoWindow); far;
  begin
    PChildWindow^.TimerTick;
  end;

begin
  ForEach(@ChildTimers);
end;

procedure TGDIDemoWindow.WMDestroy(var Msg: TMessage);
begin
  KillTimer(HWindow, 0);
  TMDIWindow.WMDestroy(Msg);
end;

{ TGDIDemoApp ------------------------------------------------------ }

type
  TGDIDemoApp = object(TApplication)
    procedure InitMainWindow; virtual;
  end;

procedure TGDIDemoApp.InitMainWindow;
begin
  { Create a main window of type TGDIWindow. }
  MainWindow := New(PGDIDemoWindow,
    Init('GDI Demo', LoadMenu(HInstance,MakeIntResource(MenuID))));
end;

var
  GDIDemoApp: TGDIDemoApp;

begin
  GDIDemoApp.Init('GDIDEMO');
  GDIDemoApp.Run;
  GDIDemoApp.Done;
end.
