unit GvClient;

{ Graphics Vision Client Window Support,
  Copr. 1995 Matthias K”ppe
}

interface

uses Objects, Views, Drivers, GvMenus, GvDialog, GVViews, GvBitmap, GvApp;

const
  cmIconize = 18;
  cmZoomClient = 19;
  cmCloseClient = 20;
  cmZoomedClient = 21;
  cmUpSelect = 22;
  cmDownSelect = 23;
  cmIconDragging = 56;
  cmNoIcon = 57;
  cmArrangeIcon = 58;
  cmIconAttach = 59;
  cmArrangeIcons = 60;
  cmSmartZoom = 61;

const
  CParentWindow = CDialog + #2#3#4#5#6#7#8#9;
  CWindowMenubar = #19#20#21#22#23#24#25#26;

type
  PIconText = ^TIconText;
  TIconText = object(TLabel)
    function GetPalette: PPalette; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
  end;

  PMovableIcon = ^TMovableIcon;
  TMovableIcon = object(TIconLabel)
    Menu: PMenu;
    TopArrange: Boolean;
    constructor Init(var Bounds: TRect;
      Resource: PStream; IconName: PChar);
    procedure DragView(Event: TEvent; Mode: Byte; var Limits: TRect;
      MinSize, MaxSize: TPoint);
    procedure Draw; virtual;
    procedure FindSpace; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure InitMenu; virtual;
    procedure Open; virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
    function Valid(Command: Word): Boolean; virtual;
  end;

  PWindowIcon = ^TWindowIcon;
  TWindowIcon = object(TMovableIcon)
    constructor Init(var Bounds: TRect;
      Resource: PStream; IconName: PChar);
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure InitMenu; virtual;
    procedure Open; virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
  end;

  PIconizeFrame = ^TIconizeFrame;
  TIconizeFrame = object(TFrame)
    Menu: PMenu;
    constructor Init(var Bounds: TRect);
    destructor Done; virtual;
    procedure Draw; virtual;
    procedure DrawIconizeButton(Down: Boolean); virtual;
    procedure DrawZoomField(Down: Boolean); virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure InitMenu; virtual;
    function Zoomed: Boolean; virtual;
  end;

  PWindowMenuBar = ^TWindowMenuBar;
  TWindowMenuBar = object(TMenuBar)
    procedure ChangeBounds(var Bounds: TRect); virtual;
    procedure GetItemRect(Item: PMenuItem; var R: TRect); virtual;
    function GetPalette: PPalette; virtual;
  end;

  PClientDesktop = ^TClientDesktop;
  TClientDesktop = object(TDesktop)
    AutoArrangeIcons: Boolean;
    constructor Init(var Bounds: TRect);
    procedure ArrangeIcons;
    procedure ChangeBounds(var Bounds: TRect); virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure SetState(AState: Word; Enable: Boolean); virtual;
  end;

  PParentWindow = ^TParentWindow;
  TParentWindow = object(TWindow)
    ClientDesktop: PClientDesktop;
    ZoomGroup: PGGroup;
    Delta: TPoint;
    HScrollbar, VScrollbar: PScrollbar;
    Icon: PWindowIcon;
    IconText: PIconText;
    Menubar: PMenuBar;
    constructor Init(var Bounds: TRect; ATitle: TTitleStr;
      ANumber: Integer; AMenu: PMenu);
    procedure CalcClientBounds(var Bounds: TRect); virtual;
    function CalcControlBounds(var Bounds: TRect): Boolean; virtual;
    procedure CalcNonClientBounds(var Bounds: TRect); virtual;
    procedure ChangeBounds(var Bounds: TRect); virtual;
    procedure Close; virtual;
    function GetPalette: PPalette; virtual;
    function GetTitle(MaxSize: Integer): TTitleStr; virtual;
    procedure HandleEvent(var Event: TEvent); virtual;
    procedure InitClientDesktop(var Bounds: TRect); virtual;
    procedure InitFrame; virtual;
    procedure InitIcon; virtual;
    procedure InitIconText; virtual;
    procedure InitMenubar(AMenu: PMenu); virtual;
    procedure InitZoomGroup(var Bounds: TRect); virtual;
    procedure InsertIn(Group: PGGroup);
    function ParentMessage(What: Word; Command: Word; InfoPtr: pointer): pointer;
    procedure ScrollTo(X, Y: Integer);
    procedure SizeLimits(var Min, Max: TPoint); virtual;
    procedure Zoom; virtual;
  private
    function CurrentWindow: PGView;
    procedure Update;
  end;

const
  IconSpace: TPoint = (X: 100; Y: 70);
  IconSize: TPoint = (X: 32; Y: 32);
  MenuHeight: Integer = 18;

implementation

uses GvDriver, MyMouse, MetaGr, ExtGraph, GVTexts,
  KeyNames, GVisible, MyFonts;

function Min (x, y: Integer): Integer; Assembler;
Asm
	MOV	AX, X
	CMP	AX, Y
	JLE	@@1
	MOV	AX, Y
@@1:
End;

function Max (x, y: Integer): Integer; Assembler;
Asm
	MOV	AX, X
	CMP 	AX, Y
	JGE	@@1
	MOV	AX, Y
@@1:
End;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

constructor TIconizeFrame.Init;
begin
  inherited Init(Bounds);
  InitMenu
end;

destructor TIconizeFrame.Done;
begin
  DisposeMenu(Menu);
  inherited Done
end;

procedure TIconizeFrame.Draw;
begin
  inherited Draw;
  DrawIconizeButton(false);
end;

procedure TIconizeFrame.DrawIconizeButton(Down: Boolean);
var
  R: TRect;
Begin
  SetViewPort;
  R.Assign(Size.X - 2 - 2 * GvViews.IconSize, 3, Size.X - 2 - GvViews.IconSize, 3 + TitleSize);
  If PWindow(GOwner)^.Flags and wfModal <> 0 then R.Move(-1, 1);
  DrawButton(R, Down, True, White, Black, LightGray, DarkGray);
  DrawIcon(R.A.X, R.A.Y, 6, $0F);
  RestoreViewPort;
End;

procedure TIconizeFrame.DrawZoomField(Down: Boolean);
var
  R: TRect;
Begin
  If PWindow(GOwner)^.Flags and wfZoom <> 0
  then Begin
    SetViewPort;
    R.Assign (Size.X-3-GvViews.IconSize, 3, Size.X-3, 3+TitleSize);
    If PWindow(GOwner)^.Flags and wfModal <> 0 then R.Move(-1, 1);
    DrawButton (R, Down, True, White, Black, LightGray, DarkGray);
    If not Zoomed then DrawIcon (R.A.X, R.A.Y, 4, $0F)
		  else DrawIcon (R.A.X, R.A.Y, 5, $0F);
    RestoreViewPort
  End
End;

procedure TIconizeFrame.HandleEvent(var Event: TEvent);
var
  R, M: TRect;
  P: TPoint;
  Down: Boolean;
  Popup: PMenuView;
  Item: PMenuItem;
  Ev: TEvent;

	procedure InvertMenuIcon;
	begin
	  SetViewport;
	  SetWriteMode(XorPut);
	  SetFillStyle(SolidFill, 15);
	  Bar(M.A.X, M.A.Y, M.B.X - 1, M.B.Y - 1);
	  SetWriteMode(NormalPut);
	  RestoreViewport
	end;

	procedure PopUpMenu;
	begin
	  M := R;
	  InvertMenuIcon;
	  R.A.Y := R.B.Y;
	  MakeGlobal(R.A, R.A);
	  Popup := New(PMenuPopup, Init(R, Menu));
	  Application^.ExecView(Popup);
	  Dispose(Popup, Done);
	  InvertMenuIcon;
	  GetEvent(Event);
	  Event.InfoPtr := GOwner;
	  PutEvent(Event);
	  ClearEvent(Event);
        end;

	Function HotK(Menus: PMenu): PMenuItem;
        { From GvMenus.PAS
        }
	var
          Item, Hot: PMenuItem;
	Begin
          HotK:=nil;
          Item:=Menus^.Items;
	  While Item<>nil do Begin
            If (Item^.Command=0) and (Item^.Name<>nil) then Begin
	      Hot:=HotK (Item^.SubMenu);
	      If Hot<>nil then HotK:=Hot;
	    End
            Else If Item^.KeyCode=Event.KeyCode then
                   If Item^.Disabled and dfDisabled = 0 then HotK:=Item;
	    Item:=Item^.Next;
	  End;
        End;

begin
  If Event.What and evMouse <> 0
  then begin
    P := Event.Where;
    MakeLocal(P, P);
    R.Assign(Size.X - 2 - 2 * GvViews.IconSize, 4, Size.X - 2 - GvViews.IconSize, 2 + TitleSize);

    If (Event.What = evMouseDown) and R.Contains(P)
    then Begin
      Down := False;
      Repeat
	MakeLocal (Event.Where, P);
	If R.Contains (P) xor Down
	then Begin
	  Down := not Down;
	  DrawIconizeButton(Down)
	End;
      Until not MouseEvent(Event, evMouseAuto);
      DrawIconizeButton(False);
      If R.Contains (P) then Begin
	Event.What := evCommand;
	Event.Command := cmIconize;
	Event.InfoPtr := GOwner;
	PutEvent(Event)
      End;
      ClearEvent(Event);
      Exit
    end;

    R.Assign(4, 4, 2 + GvViews.IconSize, 2 + TitleSize);

    If R.Contains(P) then
      If (Event.What = evMouseDown) and (Menu <> nil)
      then Begin
	PopUpMenu;
	Exit
      End else
      if (Event.What = evMouseUp) and Event.Double
      then begin
	Event.What := evCommand;
	Event.Command := cmClose;
	Event.InfoPtr := GOwner;
	PutEvent(Event);
	ClearEvent(Event);
	Exit
      end;
  end else
  if Event.What = evKeyDown
  then begin
    case Event.KeyCode of
      kbAltSpace:
	If Menu <> nil
	then begin
	  R.Assign(4, 4, 2 + GvViews.IconSize, 2 + TitleSize);
	  PopUpMenu;
	  Exit
	end;
      else begin
	Item := HotK(Menu);
	If Item <> nil
	then begin
	  Event.What := evCommand;
	  Event.Command := Item^.Command;
	  Event.InfoPtr := nil;
	  PutEvent(Event);
	  ClearEvent(Event);
	  Exit
	end
      end
    end;
  end;

  inherited HandleEvent(Event);

  GetEvent(Ev);
  If Ev.What = evCommand
  then Ev.InfoPtr := GOwner;
  PutEvent(Ev)
end;

procedure TIconizeFrame.InitMenu;
begin
  Menu := NewMenu(
    NewItemKn('~I~conize', kbAltF5, cmIconize, 0,
    NewItemKn('~Z~oom', kbF5, cmZoom, 0,
    NewItemKn('Size/~M~ove', kbCtrlF5, cmResize, 0,
    NewItemKn('~S~mart zoom', kbShiftF5, cmSmartZoom, 0,
    NewLine(
    NewItemKn('~C~lose', kbAltF3, cmClose, 0,
    NewLine(
    NewItemKn('Go ~u~p', kbAltMinus, cmUpSelect, 0,
    NewItemKn('Go ~d~own', kbAltEqual, cmDownSelect, 0,
    NewItemKn('~N~ext', kbF6, cmNext, 0,
    NewItemKn('~P~rev', kbShiftF6, cmPrev, 0,
    nil))))))))))))
end;

function TIconizeFrame.Zoomed: Boolean;
begin
  Zoomed :=
    ((GOwner <> nil) and (GOwner^.GOwner <> nil) and
     (Message(GOwner^.GOwner^.GOwner, evBroadcast, cmZoomedClient, GOwner) <> nil))
end;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

function TIconText.GetPalette: PPalette;
const
  C1: string[6] = #8#9#8#9#6#7;
  C2: string[6] = #17#18#17#18#15#16;
begin
  If GOwner = PGGroup(Desktop)
  then GetPalette := @C2
  else GetPalette := @C1
end;

procedure TIconText.HandleEvent(var Event: TEvent);
var
  R: TRect;
begin
  inherited HandleEvent(Event);
  If (Event.What = evBroadcast) and (Event.InfoPtr = Link) then
  case Event.Command of
    cmIconDragging:
      begin
	If not GetState(sfVisible)
	then begin
	  Link^.GetBounds(R);
	  MoveTo((R.A.X + R.B.X - Size.X) div 2, R.B.Y)
	end;
	If Link^.GetState(sfVisible)
	then SetState(sfVisible, not Link^.GetState(sfDragging))
      end;
    cmReceivedFocus:
      PutInFrontOf(Link);
  end else
  if (Event.What = evMouseAuto) or (Event.What = evMouseUp)
  then Link^.HandleEvent(Event)
end;

procedure TIconText.SetState(AState: Word; Enable: Boolean);
begin
  inherited SetState(AState, Enable);
  If AState = sfSelected
  then Link^.Select;
end;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

constructor TMovableIcon.Init(var Bounds: TRect;
  Resource: PStream; IconName: PChar);
begin
  inherited Init(Bounds, Resource, IconName, nil);
  EventMask := EventMask or (evMouse + evBroadcast);
  Options := Options or ofSelectable;
  TopArrange := true;
  InitMenu
end;

procedure TMovableIcon.DragView(Event: TEvent; Mode: Byte; var Limits: TRect;
  MinSize, MaxSize: TPoint);
var
  P, S: TPoint;
  SaveBounds: TRect;
  SaveOpt: Word;

 procedure MoveGrow(X1, Y1, X2, Y2: Integer);
 var
   R: TRect;
 begin
   X2 := Min(Max(X2, MinSize.X), MaxSize.X);
   Y2 := Min(Max(Y2, MinSize.Y), MaxSize.Y);
   X1 := Min(Max(X1, Limits.A.X - X2 + 1), Limits.B.X - 1);
   Y1 := Min(Max(Y1, Limits.A.Y - Y2 + 1), Limits.B.Y - 1);
   if Mode and dmLimitLoX <> 0 then X1 := Max(X1, Limits.A.X);
   if Mode and dmLimitLoY <> 0 then Y1 := Max(Y1, Limits.A.Y);
   if Mode and dmLimitHiX <> 0 then X1 := Min(X1, Limits.B.X - X2);
   if Mode and dmLimitHiY <> 0 then Y1 := Min(Y1, Limits.B.Y - Y2);
   R.Assign(X1, Y1, X1 + X2, Y1 + Y2);
   Locate(R)
 end;

 procedure Change(DX, DY: Integer);
 begin
   if (Mode and dmDragMove <> 0) and (GetShiftState and $03 = 0) then
   begin
     Inc(P.X, DX);
     Inc(P.Y, DY);
   end
 end;

 procedure Update(X, Y: Integer);
 begin
   if Mode and dmDragMove <> 0 then
   begin
     P.X := X;
     P.Y := Y;
   end;
 end;

 procedure SetMouse;
 var
   P: TPoint;
 Begin
   P.X := Size.X div 2; P.Y := Size.Y div 2;
   MakeGlobal(P, P);
   SetMousePos(P.X, P.Y);
 End;

Begin
  If (GOwner = nil) or (Mode and dmDragMove = 0) then Exit;
  SaveOpt := Options;
  Options := Options and not ofHoldFirst;
  SetState(sfDragging, True);
  Message(GOwner, evBroadcast, cmIconDragging, @Self);
  GetBounds(SaveBounds);
  SetCurrentCursor(mcMove);
  if Event.What and evMouse <> 0 then
  begin
    P.X := Origin.X - Event.Where.X;
    P.Y := Origin.Y - Event.Where.Y;
    repeat
      Inc(Event.Where.X, P.X);
      Inc(Event.Where.Y, P.Y);
      MoveGrow(Event.Where.X, Event.Where.Y, Size.X, Size.Y);
    until not MouseEvent(Event, evMouseMove);
  end else
  begin
    repeat
      P := Origin;
      S := Size;
      KeyEvent(Event);
      case Event.KeyCode and $FF00 of
	kbLeft:
	  If GetShiftState and kbCtrlShift <> 0
	  then Change(0, -WinDragAdd*8)
	  else Change(-WinDragAdd, 0);
	kbRight:
	  If GetShiftState and kbCtrlShift <> 0
	  then Change(0, WinDragAdd*8)
	  else Change(WinDragAdd, 0);
	kbUp:
	  Change(0, -WinDragAdd);
	kbDown:
	  Change(0, WinDragAdd);
	kbCtrlLeft:
	  Change(-WinDragAdd*8, 0);
	kbCtrlRight:
	  Change(WinDragAdd*8, 0);
	kbHome:
	  Update(Limits.A.X, P.Y);
	kbEnd:
	  Update(Limits.B.X - S.X, P.Y);
	kbPgUp:
	  Update(P.X, Limits.A.Y);
	kbPgDn:
	  Update(P.X, Limits.B.Y - S.Y);
      end;
      MoveGrow(P.X, P.Y, S.X, S.Y);
    until (Event.KeyCode = kbEnter) or (Event.KeyCode = kbEsc);
    if Event.KeyCode = kbEsc then Locate(SaveBounds);
  end;
  SetState(sfDragging, False);
  Options := SaveOpt;
  Message(GOwner, evBroadcast, cmIconDragging, @Self);
  SetCurrentCursor(mcNoCursor);
End;

procedure TMovableIcon.Draw;
begin
  inherited Draw;
  If (AndMask = nil) and (XorMask = nil)
  then begin
    SetFillStyle(SolidFill, GetColor(0));
    SetColor(1);
    Bar(1, 1, Size.X - 2, Size.Y - 2);
    Rectangle(0, 0, Size.X - 1, Size.Y - 1)
  end
end;

procedure TMovableIcon.FindSpace;
var
  x, y, xm: Integer;
  R: TRect;

	function DoesOverlap(View: PGView): Boolean; {$ifndef FPK}far;{$endif}
	var
	  VR: TRect;
	begin
	  If View^.Valid(cmNoIcon)
	  then DoesOverlap := false
	  else begin
	    View^.GetBounds(VR);
	    VR.Intersect(R);
	    DoesOverlap := not VR.Empty
	  end
	end;

	procedure Loc(var R: TRect);
	begin
	  Message(GOwner, evBroadcast, cmIconDragging, @Self);
	  SetState(sfDragging, false);
	  MoveTo((R.B.X + R.A.X - Size.x) div 2,
	    (R.B.Y + R.A.Y - Size.y) div 2);
	  Message(GOwner, evBroadcast, cmIconDragging, @Self);
	end;

begin
  If GOwner = nil then Exit;
  y := 0;
  xm := GOwner^.Size.x div IconSpace.x - 1;
  If xm < 1
  then xm := 1;
  Repeat
    For x := 0 to xm do
    begin
      If TopArrange
      then R.Assign(x * IconSpace.x, y * IconSpace.y,
	(x + 1) * IconSpace.x, (y + 1) * IconSpace.y)
      else R.Assign(x * IconSpace.x, GOwner^.Size.y - (y + 1) * IconSpace.y,
	(x + 1) * IconSpace.x, GOwner^.Size.y - y * IconSpace.y);
      If GOwner^.FirstThat(@DoesOverlap) = nil
      then begin
	Loc(R);
	Exit
      end
    end;
    Inc(y);
  Until false
end;

procedure TMovableIcon.HandleEvent(var Event: TEvent);
var
  R: TRect;
  P, Min, Max: TPoint;
  Popup: PMenuView;
begin
  inherited HandleEvent(Event);
  If Event.What = evMouseUp
  then begin
    if Event.Double
    then begin
      Open;
      ClearEvent(Event)
    end
    else
      if Menu <> nil
      then begin
	R.Assign(0, 0, 0, 0);
	Popup := New(PMenuPopup, Init(R, Menu));
	P.X := 0; P.Y := 0;
	MakeGlobal(P, P);
	with Popup^ do
	  MoveTo(P.X, P.Y - Size.Y);
	Message(Popup, evBroadcast, cmCommandSetChanged, nil);
	Application^.ExecView(Popup);
	ClearEvent(Event)
      end
  end else
  If Event.What = evMouseAuto
  then begin
    GOwner^.GetExtent(R);
    SizeLimits(Min, Max);
    DragView(Event, DragMode or dmDragMove, R, Min, Max);
    ClearEvent(Event)
  end else
  if (Event.What = evKeyboard) and
     ((Event.KeyCode = kbEnter) or
      ((Event.KeyCode = kbAltSpace) and (Menu <> nil)))
  then Open
  else
  if (Event.What = evBroadcast) and (Event.Command = cmIconDragging) and
    (Event.InfoPtr = nil)
  then SetState(sfDragging, true) else
  if (Event.What = evBroadcast) and (Event.Command = cmArrangeIcon)
  then FindSpace
end;

procedure TMovableIcon.InitMenu;
begin
  Menu := nil
end;

procedure TMovableIcon.Open;
begin
end;

procedure TMovableIcon.SetState(AState: Word; Enable: Boolean);
begin
  inherited SetState(AState, Enable);
  If AState = sfSelected
  then DrawView
end;

function TMovableIcon.Valid(Command: Word): Boolean;
begin
  If Command = cmNoIcon
  then Valid := GetState(sfDragging)
  else Valid := inherited Valid(Command)
end;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

constructor TWindowIcon.Init(var Bounds: TRect;
  Resource: PStream; IconName: PChar);
begin
  inherited Init(Bounds, Resource, IconName);
  Options := Options or ofTopSelect
end;

procedure TWindowIcon.HandleEvent(var Event: TEvent);
var
  R: TRect;
  P, Min, Max: TPoint;
  Popup: PMenuView;
begin
  inherited HandleEvent(Event);
  If Event.What = evCommand
  then
    case Event.Command of
      cmResize:
	Begin
	  GOwner^.GetExtent(R);
	  SizeLimits(Min, Max);
	  DragView(Event, DragMode or wfMove, R, Min, Max);
	  ClearEvent(Event)
	End;
      cmIconize:
	begin
	  Message(GOwner, evBroadcast, cmIconize, @Self);
	  ClearEvent(Event)
	end;
      cmZoom, cmSmartZoom:
	begin
	  PutEvent(Event);
	  Message(GOwner, evBroadcast, cmIconize, @Self);
	  ClearEvent(Event)
	end;
      cmClose:
	begin
	  Message(GOwner, evBroadcast, cmClose, @Self);
	  ClearEvent(Event)
	end
    end
end;

procedure TWindowIcon.InitMenu;
begin
  Menu := NewMenu(
    NewItemKn('~R~estore', kbAltF5, cmIconize, 0,
    NewItemKn('~Z~oom', kbF5, cmZoom, 0,
    NewItemKn('Size/~M~ove', kbCtrlF5, cmResize, 0,
    NewItemKn('~S~mart zoom', kbShiftF5, cmSmartZoom, 0,
    NewLine(
    NewItemKn('~C~lose', kbAltF3, cmClose, 0,
    NewLine(
    NewItemKn('Go ~u~p', kbAltMinus, cmUpSelect, 0,
    nil)))))))))
end;

procedure TWindowIcon.Open;
begin
  Message(GOwner, evBroadcast, cmIconize, @Self);
end;

procedure TWindowIcon.SetState(AState: Word; Enable: Boolean);
begin
  inherited SetState(AState, Enable);
  If AState = sfSelected
  then
    if Enable
    then EnableCommands([cmZoom, cmSmartZoom, cmResize, cmClose, cmNext, cmPrev])
    else DisableCommands([cmZoom, cmSmartZoom, cmResize, cmClose, cmNext, cmPrev]);
end;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

procedure TWindowMenuBar.ChangeBounds(var Bounds: TRect);
var
  SaveBounds: TRect;
  Item: PMenuItem;
  R: TRect;
begin
  Item := Menu^.Items;
  If Item <> nil
  then begin
    GetBounds(SaveBounds);
    SetBounds(Bounds);
    while Item^.Next <> nil do
      Item := Item^.Next;
    GetItemRect(Item, R);
    Bounds.B.Y := Bounds.A.Y + R.B.Y + 1;
    SetBounds(SaveBounds)
  end;
  inherited ChangeBounds(Bounds)
end;

procedure TWindowMenuBar.GetItemRect(Item: PMenuItem; var R: TRect);
var
  I, N: PMenuItem;
  W, X, Y: Integer;

	function Width(Item: PMenuItem): Integer;
	begin
	  If Item^.Disabled and dfBitmap <> 0
	  then Width := Integer(Item^.Name^)
	  else Width := TextWidth(PString(Item^.Name)^)
	end;

Begin
  SetTextParams (ftSystem,0,0,True);
  N := Menu^.Items;
  X := 6;
  Y := 0;
  repeat
    I := N;
    W := Width(I);
    Inc(X, W + 12);
    If X > Size.X
    then begin
      X := 18 + W;
      Inc(Y, MenuHeight)
    end;
    N := I^.Next
  until I = Item;
  R.Assign(X - W - 12, Y, X, Y + MenuHeight);
End;

function TWindowMenuBar.GetPalette: PPalette;
const
  P: string[Length(CWindowMenuBar)] = CWindowMenuBar;
begin
  If GOwner = PGGroup(Application)
  then GetPalette := inherited GetPalette
  else GetPalette := @P
end;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

constructor TClientDesktop.Init(var Bounds: TRect);
begin
  inherited Init(Bounds);
  AutoArrangeIcons := true;
end;

procedure TClientDesktop.ArrangeIcons;
var
  x, y, xm: Integer;
  P: PGView;
  R: TRect;

	function IsIcon(P: PGView): Boolean;
	begin
	  If not P^.Valid(cmNoIcon)
	  then IsIcon := PMovableIcon(P)^.TopArrange
	  else IsIcon := false
	end;

	function NextIcon(P: PGView): PGView;
	begin
	  repeat
	    P := P^.NextView
	  until (P = nil) or IsIcon(P);
	  NextIcon := P
	end;

	procedure Loc(P: PGView; var R: TRect);
	begin
	  P^.SetState(sfDragging, true);
	  Message(@Self, evBroadcast, cmIconDragging, P);
	  P^.MoveTo((R.B.X + R.A.X - Size.x) div 2,
	    (R.B.Y + R.A.Y - Size.y) div 2);
	  P^.SetState(sfDragging, false);
	  Message(@Self, evBroadcast, cmIconDragging, P)
	end;

begin
  Lock;
  If IsIcon(First)
  then P := First
  else P := NextIcon(First);

  y := 0;
  xm := Size.x div IconSpace.x - 1;
  If xm < 1
  then xm := 1;

  Repeat
    For x := 0 to xm do
    begin
      If P = nil
      then begin
	Unlock;
	Exit
      end;
      R.Assign(x * IconSpace.x, y * IconSpace.y,
	(x + 1) * IconSpace.x, (y + 1) * IconSpace.y);
      Loc(P, R);
      P := NextIcon(P)
    end;
    Inc(y);
  Until false
end;

procedure TClientDesktop.ChangeBounds(var Bounds: TRect);
begin
  inherited ChangeBounds(Bounds);
  If AutoArrangeIcons
  then ArrangeIcons
end;

procedure TClientDesktop.HandleEvent(var Event: TEvent);
begin
  If Event.What = evCommand
  then
    case Event.Command of
      cmArrangeIcons:
	ArrangeIcons;
      cmNext:
	If Current <> nil
	then begin
	  SelectNext(false);
	  ClearEvent(Event)
	end;
      cmPrev:
	If Current <> nil
	then begin
	  SelectNext(true);
	  ClearEvent(Event)
	end;
    end;
  inherited HandleEvent(Event);
end;

procedure TClientDesktop.SetState(AState: Word; Enable: Boolean);
begin
  If AState = sfActive then
  If Current <> nil then Current^.SetState(AState, Enable) else
  else inherited SetState(AState, Enable)
end;

{ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
}

constructor TParentWindow.Init(var Bounds: TRect;
  ATitle: TTitleStr; ANumber: Integer; AMenu: PMenu);
var
  R: TRect;
begin
  inherited Init(Bounds, ATitle, ANumber);
  GrowMode := 0;
  InitMenubar(AMenu);
  HScrollbar := StandardScrollbar(sbHorizontal);
  HScrollbar^.SetStep(10, 1);
  VScrollbar := StandardScrollbar(sbVertical);
  VScrollbar^.SetStep(10, 1);
  If Menubar <> nil
  then begin
    Inc(VScrollbar^.Origin.Y, Menubar^.Size.Y);
    Dec(VScrollbar^.Size.Y, Menubar^.Size.Y)
  end;
  CalcClientBounds(R);
  InitClientDesktop(R);
  Insert(ClientDesktop);
  GetClientRect(R);
  InitZoomGroup(R);
  Insert(ZoomGroup);
  Insert(Menubar);
  InitIcon;
  InitIconText;
end;

procedure TParentWindow.CalcClientBounds(var Bounds: TRect);
begin
  GetClientRect(Bounds);
  If Menubar <> nil
  then Inc(Bounds.A.Y, Menubar^.Size.Y);
  If not HScrollbar^.GetState(sfDisabled)
  then Bounds.B.Y := HScrollbar^.Origin.Y;
  If not VScrollbar^.GetState(sfDisabled)
  then Bounds.B.X := VScrollbar^.Origin.X
end;

function TParentWindow.CalcControlBounds(var Bounds: TRect): Boolean;
var
  R: TRect;

	procedure DoUnite(P: PGView); {$ifndef FPK}far;{$endif}
	var
	  R: TRect;
	begin
	  If (P^.GetState(sfVisible)) and (TypeOf(P^) <> TypeOf(TBackground))
	  then begin
	    If P^.GetState(sfDragging)
	    then CalcControlBounds := false;
	    P^.GetBounds(R);
	    if Bounds.Empty
	    then Bounds := R
	    else Bounds.Union(R)
	  end
	end;

begin
  CalcControlBounds := true;
  Bounds.Assign(0, 0, 0, 0);
  ClientDesktop^.ForEach(@DoUnite);
  Bounds.Move(Delta.X, Delta.Y);
  R.Assign(0, 0, 0, 0);
  Bounds.Union(R);
end;

procedure TParentWindow.CalcNonClientBounds(var Bounds: TRect);
var
  Client, Extent: TRect;
begin
  GetClientRect(Client);
  GetExtent(Extent);
  If Menubar <> nil
  then Dec(Bounds.A.Y, Menubar^.Size.Y);
  Dec(Bounds.A.Y, Client.A.Y);
  Dec(Bounds.A.X, Client.A.X);
  Inc(Bounds.B.X, Extent.B.X - Client.B.X);
  Inc(Bounds.B.Y, Extent.B.Y - Client.B.Y);
end;

procedure TParentWindow.ChangeBounds(var Bounds: TRect);
var
  MB, ZB: TRect;
begin
  inherited ChangeBounds(Bounds);
  If (Menubar <> nil) and (ZoomGroup <> nil)
  then begin
    Menubar^.GetBounds(MB);
    ZoomGroup^.GetBounds(ZB);
    ZB.A.Y := MB.B.Y - 19;
    ZoomGroup^.Locate(ZB)
  end
end;

procedure TParentWindow.Close;
begin
  If Valid(cmClose)
  then begin
    ParentMessage(evCommand, cmCloseClient, @Self);
    If Icon <> nil then Icon^.Free;
    If IconText <> nil then IconText^.Free;
    Free;
  end;
end;

function TParentWindow.CurrentWindow: PGView;
begin
  If ZoomGroup^.Current <> nil
  then CurrentWindow := ZoomGroup^.Current
  else CurrentWindow := ClientDesktop^.Current
end;

function TParentWindow.GetPalette: PPalette;
const
  P1: string[Length(CParentWindow)] = CParentWindow;
  P2: char = #0;
begin
  If GOwner = PGGroup(Desktop)
  then GetPalette := @P1
  else GetPalette := PPalette(@P2)
end;

function TParentWindow.GetTitle(MaxSize: Integer): TTitleStr;
begin
  If ZoomGroup^.First <> nil
  then GetTitle := inherited GetTitle(MaxSize) + ' - [' +
    PWindow(ZoomGroup^.First)^.GetTitle(MaxSize) + ']'
  else GetTitle := inherited GetTitle(MaxSize)
end;

procedure TParentWindow.HandleEvent(var Event: TEvent);
var
  P: PWindow;
  R: TRect;

	procedure DoIconize(Enable: Boolean);

		procedure SetVis(P: PGView; Enable: Boolean);
		var
		  Save: Word;
		begin
		  If P <> nil then
		  with P^ do
		  begin
		    Save := Options;
		    Options := Options and not ofSelectable;
		    P^.SetState(sfVisible, Enable);
		    Options := Save
		  end
		end;

	begin
	  SetState(sfVisible, not Enable);
	  If Enable
	  then begin
	    If Icon <> nil then Icon^.PutInFrontOf(GOwner^.Last);
	    If IconText <> nil then IconText^.PutInFrontOf(Icon);
	  end
	  else Select;
	  SetVis(Icon, Enable);
	  SetVis(IconText, Enable);
	end;

	procedure UpSelect;
	var
	  Cur: PGView;
        begin
          Cur := CurrentWindow;
          If GetState(sfActive) and (Cur <> nil)
	  then begin
	    Cur^.GOwner^.SetState(sfActive, false);
	    Cur^.GOwner^.SetState(sfFocused, false);
	    Cur^.SetState(sfSelected, false);
            Cur^.GOwner^.Current := nil;
            SetState(sfSelected, true);
	    ClearEvent(Event)
          end
        end;

        procedure DownSelect;
        var
          Cur: PGView;
        begin
          Cur := CurrentWindow;
	  If GetState(sfActive) and (Cur = nil)
          then begin
            If ZoomGroup^.Last <> nil
            then ZoomGroup^.First^.Focus else
	    if ClientDesktop^.Last <> nil
	    then ClientDesktop^.First^.Focus else
	    Exit;
	    ClearEvent(Event)
	  end
	end;

	procedure SmartZoom;
	var
	  R: TRect;
	begin
	  CalcControlBounds(R);
	  CalcNonClientBounds(R);
	  If HScrollbar <> nil
	  then HScrollbar^.SetState(sfDisabled, true);
	  If VScrollbar <> nil
	  then VScrollbar^.SetState(sfDisabled, true);
	  GrowTo(R.B.X - R.A.X, R.B.Y - R.A.Y)
	end;

	procedure TileBounds(var R: TRect);

		procedure Dis(P: PGView);
		begin
		  P^.State := P^.State or sfDisabled
		end;

	begin
	  Dis(HScrollbar);
	  Dis(VScrollbar);
	  CalcClientBounds(R);
	  Dec(R.B.X, R.A.X); R.A.X := 0;
	  Dec(R.B.Y, R.A.Y); R.A.Y := 0;
	end;

	procedure Tile;
	var
	  R: TRect;
	begin
	  If ClientDesktop^.Current = nil then Exit;
	  TileBounds(R);
	  ClientDesktop^.Tile(R);
	  ClearEvent(Event)
	end;

	procedure Cascade;
	var
	  R: TRect;
	begin
	  If ClientDesktop^.Current = nil then Exit;
	  TileBounds(R);
	  ClientDesktop^.Cascade(R);
	  ClearEvent(Event)
	end;

begin
  If (Event.What = evCommand) and
     (Event.InfoPtr <> nil) and (Event.InfoPtr <> @Self)
  then
    case Event.Command of
      cmZoom:
	begin
	  TGGroup.HandleEvent(Event);
	  Exit
	end;
    end;
  inherited HandleEvent(Event);
  If Event.What = evTimer
  then Update else
  if Event.What = evCommand
  then
    case Event.Command of
      cmIconize:
	If (Event.InfoPtr = nil) or (Event.InfoPtr = @Self)
	then begin
	  P := ParentMessage(evCommand, cmCloseClient, @Self);
	  If Icon <> nil then DoIconize(GetState(sfVisible));
	  If P <> nil
	  then begin
	    Locate(ZoomRect);
	    PParentWindow(P)^.ClientDesktop^.Insert(@Self)
	  end;
	  ClearEvent(Event)
	end;
      cmSmartZoom:
	If ClientDesktop^.Current <> nil
	then begin
	  SmartZoom;
	  ClearEvent(Event)
	end;
      cmCloseClient:
	begin
	  P := PWindow(Event.InfoPtr);
	  If P^.GOwner = ZoomGroup
	  then begin
	    ZoomGroup^.Delete(P);
	    If ZoomGroup^.Last = nil
	    then begin
	      ZoomGroup^.Hide;
	      Menubar^.GetBounds(R);
	      R.Grow(+19, 0);
	      Menubar^.Locate(R);
	      Frame^.DrawView;
	      ClearEvent(Event)
	    end
	  end
	end;
      cmZoomClient:
	begin
	  P := PWindow(Event.InfoPtr);
	  If Menubar <> nil then
	  If P^.GOwner = PGGroup(ClientDesktop)
	  then begin
	    ClientDesktop^.Delete(P);
	    P^.GetBounds(R);
	    P^.ZoomRect := R;
	    ZoomGroup^.GetExtent(R);
	    R.Grow(4, 4);
	    P^.Locate(R);
	    ZoomGroup^.Insert(P);
	    Menubar^.GetBounds(R);
	    R.Grow(-19, 0);
	    Menubar^.Locate(R);
	    ZoomGroup^.Show;
	    P^.GrowMode := gfGrowHiX + gfGrowHiY;
	    P^.DragMode := dmDragMove + dmDragGrow;
	    Frame^.DrawView;
	    ClearEvent(Event)
	  end else
	  If P^.GOwner = ZoomGroup
	  then begin
	    P^.GrowMode := 0;
	    ZoomGroup^.Hide;
	    Menubar^.GetBounds(R);
	    R.Grow(+19, 0);
	    Menubar^.Locate(R);
	    ZoomGroup^.Delete(P);
	    P^.Locate(P^.ZoomRect);
	    ClientDesktop^.Insert(P);
	    Frame^.DrawView;
	    ClearEvent(Event)
	  end
	end;
      cmUpSelect:
	UpSelect;
      cmDownSelect:
	DownSelect;
      cmTile:
	Tile;
      cmCascade:
	Cascade;
    end;
  if Event.What = evBroadcast then
    if (Event.Command = cmScrollbarChanged) and
       ((Event.InfoPtr = HScrollbar) or (Event.InfoPtr = VScrollbar))
      then ScrollTo(HScrollbar^.Value, VScrollbar^.Value) else
    if (Event.Command = cmIconize) and (Icon <> nil) and (Icon = Event.InfoPtr)
      then begin
	DoIconize(GetState(sfVisible));
	ClearEvent(Event)
      end else
    if (Event.Command = cmClose) and (Icon <> nil) and (Icon = Event.InfoPtr)
      then begin
	Close;
	ClearEvent(Event)
      end else
    if (Event.Command = cmZoomedClient) and
       (PGView(Event.InfoPtr)^.GOwner = ZoomGroup)
      then ClearEvent(Event)
end;

procedure TParentWindow.InitClientDesktop(var Bounds: TRect);
begin
  ClientDesktop := New(PClientDesktop, Init(Bounds));
  with ClientDesktop^ do
    GrowMode := gfGrowHiX + gfGrowHiY;
end;

procedure TParentWindow.InitFrame;
var
  R: TRect;
begin
  GetExtent(R);
  Frame := New(PIconizeFrame, Init(R))
end;

procedure TParentWindow.InitIcon;
var
  R: TRect;
begin
  R.Assign(0, 0, IconSize.x, IconSize.y);
  Icon := New(PWindowIcon, Init(R, nil, nil));
  Icon^.Hide
end;

procedure TParentWindow.InitIconText;
var
  R: TRect;
  x: Integer;
begin
  If Icon <> nil
  then begin
    Icon^.GetBounds(R);
    R.A.Y := R.B.Y;
    x := (R.A.X + R.B.X) div 2;
    R.A.X := x - 48;
    R.B.X := x + 48;
    R.B.Y := R.A.Y + 16;
    IconText := New(PIconText, Init(R, ^c + GetTitle(255), Icon));
    IconText^.Hide
  end
end;

procedure TParentWindow.InitMenubar(AMenu: PMenu);
var
  R: TRect;
begin
  If AMenu <> nil
  then begin
    GetClientRect(R);
    R.B.Y := R.A.Y + 19;
    Menubar := New(PWindowMenuBar, Init(R, AMenu))
  end
end;

procedure TParentWindow.InitZoomGroup(var Bounds: TRect);
var
  R: TRect;
begin
  New(ZoomGroup, Init(Bounds));
  with ZoomGroup^ do
  begin
    GrowMode := gfGrowHiX + gfGrowHiY;
    Hide
  end
end;

procedure TParentWindow.InsertIn(Group: PGGroup);
begin
  If Group <> nil then
  with Group^ do
  begin
    Insert(Icon);
    Insert(IconText);
    Insert(@Self);
    Icon^.FindSpace;
  end;
end;

function TParentWindow.ParentMessage(What: Word;
  Command: Word; InfoPtr: pointer): pointer;
var
  P: pointer;
begin
  If GOwner = nil
  then ParentMessage := nil
  else begin
    P := Message(GOwner, What, Command, InfoPtr);
    If P = nil
    then P := Message(GOwner^.GOwner, What, Command, InfoPtr);
    ParentMessage := P
  end
end;

procedure TParentWindow.ScrollTo(X, Y: Integer);
var
  D: TPoint;

	procedure DoMove(P: PGView); {$ifndef FPK}far;{$endif}
	begin
	  If TypeOf(P^) <> TypeOf(TBackGround) then
          with P^ do
          begin
	    Inc(Origin.X, D.X);
	    Inc(Origin.Y, D.Y)
	  end
	end;

begin
  D.X := Delta.X - X;
  D.Y := Delta.Y - Y;
  ClientDesktop^.ForEach(@DoMove);
  Delta.X := X;
  Delta.Y := Y;
  ScrollGroup(ClientDesktop, D)
  {ClientDesktop^.ReDraw}
end;

procedure TParentWindow.SizeLimits(var Min, Max: TPoint);
begin
  Min := GMinWinSize;
  If (GOwner = PGGroup(Application)) or (GOwner = PGGroup(Desktop))
  then Max := GOwner^.Size
  else LongInt(Max) := $3FFF3FFF
end;

procedure TParentWindow.Update;
var
  C, R: TRect;
  H, V: Boolean;
  HB, VB: TRect;

	function Dis(P: PGView): Boolean;
	begin
	  Dis := P^.State and sfDisabled <> 0;
	  P^.State := P^.State or sfDisabled
	end;

	procedure Re(P: PGView; S: Boolean);
	begin
	  If not S
	  then P^.State := P^.State and not sfDisabled
	end;

begin
  If CalcControlBounds(R)
  then begin
    H := Dis(HScrollbar);
    V := Dis(VScrollbar);
    CalcClientBounds(C);
    Re(HScrollbar, H);
    Re(VScrollbar, V);
    HScrollbar^.SetRange(R.A.X, R.A.X + Max(R.B.X - R.A.X - (C.B.X - C.A.X), 0));
    VScrollbar^.SetRange(R.A.Y, R.A.Y + Max(R.B.Y - R.A.Y - (C.B.Y - C.A.Y), 0));
    H := not HScrollbar^.GetState(sfDisabled);
    V := not VScrollbar^.GetState(sfDisabled);
    HScrollbar^.GetBounds(HB);
    VScrollbar^.GetBounds(VB);
    If H and V
    then begin
      HB.B.X := VB.A.X + 1;
      VB.B.Y := HB.A.Y + 1
    end else
    if H and not V
    then
      HB.B.X := VB.B.X
    else
    if not H and V
    then
      VB.B.Y := HB.B.Y;
    If Menubar <> nil
    then VB.A.Y := MenuBar^.Size.Y + MenuBar^.Origin.Y - 1;

    ScrBBck^.SetState(sfVisible, H and V);
    HScrollbar^.SetState(sfVisible, H);
    VScrollbar^.SetState(sfVisible, V);
    HScrollbar^.Locate(HB);
    VScrollbar^.Locate(VB);
    CalcClientBounds(R);
    ClientDesktop^.Locate(R)
  end;
end;

procedure TParentWindow.Zoom;
begin
  If ParentMessage(evCommand, cmZoomClient, @Self) = nil
  then inherited Zoom;
end;

end.
