unit ViewBuilderu;{v0.57 view builder}

interface
uses
  Windows, Messages, Classes, Controls, Graphics, StdCtrls, ExtCtrls, Forms,
  UltType, WinUtl, RectValsu;

type
  TControlHandler = class(TComponent)
    constructor Create(AOwner: TWinControl); reintroduce;
    procedure ControlMouseDown(Sender: TObject;
      Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  end;

  TLevelDefCol = class(TComponent)
  public
    MaxLeft: integer;
    MaxWidth: integer;
    MaxHeight: integer;
    Pad: TRectVals;
    constructor Create(Owner: TComponent); reintroduce;
    destructor Destroy; override;
    procedure Update(AControl: TControl);
    procedure Justify(AControl: TControl);
  end;

  TLevelDef =  class(TComponent)
  private
    function ColAdd: TLevelDefCol;
    function GetCol(Index: integer): TLevelDefCol;
    function GetColCount: integer;
    function GetMaxHeight: integer;
    function GetMaxWidth: integer;
  public
    property ColCount: integer read GetColCount;
    property Cols[Index: integer]: TLevelDefCol read GetCol;
    property MaxHeight: integer read GetMaxHeight;
    property MaxWidth: integer read GetMaxWidth;
  end;

  TLevelDefs = class(TComponent)
  private
    function GetLevelDef(Index: integer): TLevelDef;
    function GetLevelDefCount: integer;
  public
    function DefAdd: TLevelDef;
    property Count: integer read GetLevelDefCount;
    property Defs[Index: integer]: TLevelDef read GetLevelDef; default;
  end;

  TViewBuilder = class;
  TViewRow = class;
  TViewCol = class(TComponent)
    { holds info about added child control and its position inside the
      view, row }
  private
    {FLeft, FTop, FHeight: integer;}
    FControl: TControl;
    procedure SetControl(AControl: TControl);
    function GetRow: TViewRow;
    function GetColIndex: integer;
  public
    procedure UpdateFromControl;
    property Control: TControl read FControl write SetControl;
    property Row: TViewRow read GetRow;
    property ColIndex: integer read GetColIndex;
  end;

  TViewRow = class(TComponent)
    { Row of TViewCol, each Row can have different number of columns; }
  private
    FLevel: integer;
    FActiveCol: TViewCol;
    function GetColCount: integer;
    function GetCol(Index: integer): TViewCol;
    function GetBuilder: TViewBuilder;
  public
    function ColAdd: TViewCol;
    property Level: integer read FLevel write FLevel;
      { indent level for this row (rows with the same indent level should look similar) }
    property ColCount: integer read GetColCount;
    property Cols[Index: integer]: TViewCol read GetCol;
    property ActiveCol: TViewCol read FActiveCol;
    property Builder: TViewBuilder read GetBuilder;
  end;

  TViewBuilder = class(TComponent)
    { list of indent Rows (tlist) }
  private
    FView: TWinControl;
    FViewOwner: TComponent;
    FOwner: TComponent;
    FLast: TControl;

    FHMargin: integer;
    FVMargin: integer;
    FCurLeft: integer;
    FCurTop: integer;
    FRowHeight: integer;
    FActiveLevel: integer;
    FMaxLevel: integer;
    FActiveRow: TViewRow;
    FLevelDefs: TLevelDefs;
    procedure CheckViewSize;
    procedure SetCaption(AChild: TControl; const ACaption: string);
    procedure UpdateChildFont(AChild: TControl);
    function GetRow(Index: integer): TViewRow;
    function GetRowCount: integer;
    procedure SetActiveLevel(ALevel: integer);
    function GetLevelDefs: TLevelDefs;
    procedure FixSizes;
  public

    constructor Create(AView: TWinControl); reintroduce;
    destructor Destroy; override;
    function RowAdd: TViewRow;
    function BevelAdd: TBevel;
      { add line separator below current row; RowAdd call should follow }

    function ChildAdd(AChildClass: TControlClass; const ACaption: string): TControl;
    {function AddBelow(AChildClass: TControlClass; const ACaption: string): TControl;
    function AddRight(AChildClass: TControlClass; const ACaption: string): TControl; }
    procedure Start;
    procedure Stop;
    property ActiveRow: TViewRow read FActiveRow write FActiveRow;
    property ActiveLevel: integer read FActiveLevel write FActiveLevel;
    property RowCount: integer read GetRowCount;
    property Rows[Index: integer]: TViewRow read GetRow;
    property LevelDefs: TLevelDefs read GetLevelDefs;
    property View: TWinControl read FView;
  end;

implementation

{TControlHandler.}
constructor TControlHandler.Create(AOwner: TWinControl);
begin
  inherited Create(AOwner);
  if AOwner is TPanel then
    TPanel(AOwner).OnMouseDown := ControlMouseDown;
end;

procedure TControlHandler.ControlMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
const
  SC_DragMove = $F012;{61458}
  {
    F001 - size control left
    F002 - size control right
    F003 - size control top
    F004 - size control left top
    F005 - size control top right
    F006 - size control down
    F007 - size control left down
    F008 - size control right down
    F009 - move control
    F00A - nothing
    F00B - size control top
    F00C - mouse up/down size control right edge(left/right)

  }
begin {winutl}
  ReleaseCapture;
  TControl(Owner).Perform(WM_SysCommand,{v0.22}SC_DragMove{/v0.22 $F00D}, 0);{MESSAGES WINDOWS}
end;

{/TControlHandler.}
{TLevelDefCol.}
constructor TLevelDefCol.Create(Owner: TComponent);
begin
  inherited;
  Pad := TRectVals.Create;
  Pad.All := 1;
end;

destructor TLevelDefCol.Destroy;
begin
  Pad.Free;
  inherited;
end;

procedure TLevelDefCol.Update(AControl: TControl);
begin
  MaxLeft := AControl.Left;
  if AControl.Width + Pad.Left + Pad.Right > MaxWidth then
    MaxWidth := AControl.Width + Pad.Left + Pad.Right;
  if AControl.Height + Pad.Top + Pad.Bottom > MaxHeight then
    MaxHeight := AControl.Height + Pad.Top + Pad.Bottom;
end;
procedure TLevelDefCol.Justify(AControl: TControl);
begin
 { if AControl.Width < MaxWidth then begin
    AControl.Left := AControl.Left + (MaxWidth - AControl.Width) div 2;
  end;}
  if AControl.Height < MaxHeight then begin
    AControl.Top := AControl.Top + (MaxHeight - AControl.Height) div 2;
  end;
end;
{/TLevelDefCol.}

{TLevelDef.}
function TLevelDef.ColAdd: TLevelDefCol;
begin
  Result := TLevelDefCol.Create(Self);
end;

function TLevelDef.GetCol(Index: integer): TLevelDefCol;
begin
  while Index >= ComponentCount do
    ColAdd;
  Result := TLevelDefCol(Components[Index]);
end;

function TLevelDef.GetColCount: integer;
begin
  Result := ComponentCount;
end;

function TLevelDef.GetMaxWidth: integer;
var c: TLevelDefCol;
begin
  Result := 0;
  if ColCount > 0 then begin
    c := Cols[ColCount - 1];
    Result := c.MaxLeft + c.MaxWidth;
  end;
end;

function TLevelDef.GetMaxHeight: integer;
var i: integer;
begin
  Result := 0;
  for i := 0 to ColCount - 1 do begin
    if Cols[i].MaxHeight > Result then
      Result := Cols[i].MaxHeight;
  end;
end;

{/TLevelDef.}

{TLevelDefs.}
function TLevelDefs.GetLevelDef(Index: integer): TLevelDef;
begin
  while Index >= ComponentCount do
    DefAdd;
  Result := TLevelDef(Components[Index]);
end;

function TLevelDefs.GetLevelDefCount: integer;
begin
  Result := ComponentCount;
end;

function TLevelDefs.DefAdd: TLevelDef;
begin
  Result := TLevelDef.Create(Self);
end;
{/TLevelDefs.}

{TViewCol.}
procedure TViewCol.SetControl(AControl: TControl);
begin
  FControl := AControl;
  UpdateFromControl;
end;

procedure TViewCol.UpdateFromControl;
var
  r: TViewRow;
  b: TViewBuilder;
begin
  r := Row;
  if r.Level >= 0 then begin
    b := r.Builder;
    b.LevelDefs[r.Level].Cols[ColIndex].Update(FControl);
  end;
end;

function TViewCol.GetRow: TViewRow;
begin
  Result := TViewRow(Owner);
end;

function TViewCol.GetColIndex: integer;
var
  r: TViewRow;
  i:integer;
  c:integer;
begin
  Result := -1;
  r := Row;
  c := r.ColCount;
  for i := 0 to c - 1 do begin
    if r.Cols[i] = Self then begin
      Result := i;
      exit;
    end;
  end;
end;

{/TViewCol.}

{TViewRow.}

function TViewRow.GetColCount: integer;
begin
  Result := ComponentCount;
end;

function TViewRow.GetCol(Index: integer): TViewCol;
begin
  Result := TViewCol(Components[Index]);
end;

function TViewRow.ColAdd: TViewCol;
begin
  Result := TViewCol.Create(Self);
  FActiveCol := Result;
end;

function TViewRow.GetBuilder: TViewBuilder;
begin
  Result := TViewBuilder(Owner);
end;

{/TViewRow.}

{TViewBuilder.}
constructor TViewBuilder.Create(AView: TWinControl);
begin
  inherited Create(nil);
  FView := AView;
  FOwner := FView{.Owner};
  FViewOwner := FView.Owner;

  FHMargin := 4;
  FVMargin := 4;

  Start;
end;


procedure TViewBuilder.CheckViewSize;
begin
  if FLast.Left + FLast.Width + FVMargin > FView.Width then
    FView.Width := FLast.Left + FLast.Width + FVMargin;
  if FLast.Top + FLast.Height + FHMargin > FView.Height then
    FView.Height := FLast.Top + FLast.Height + FHMargin;
    
  if FRowHeight < FLast.Height then
    FRowHeight := FLast.Height;
end;

function TViewBuilder.GetRow(Index: integer): TViewRow;
begin
  Result := TViewRow(Components[Index]);
end;

function TViewBuilder.GetRowCount: integer;
begin
  Result := ComponentCount;
end;

function TViewBuilder.RowAdd: TViewRow;
begin
  Result := TViewRow.Create(Self);
  Result.Level := ActiveLevel;
  FCurLeft := FVMargin;
  if FLast <> nil then begin
    FCurTop := FLast.Top + FRowHeight + FHMargin;
  end else begin
    FCurTop := FHMargin;
  end;
  FLast := nil;
  FRowHeight := 0;
  ActiveRow := Result;
end;

function TViewBuilder.BevelAdd: TBevel;
var l:integer;
begin
  l := ActiveLevel;
  try
    ActiveLevel := 0;
    RowAdd;
    with TLabel(ChildAdd(TLabel, '--------------------------------') )do begin
      Font.Name := 'Arial';
      Font.Size := 4;
      Height := 4;
      {Anchors := [akLeft, akRight];
      Width := FView.Width;
      Height := 16;
      ActiveRow.ActiveCol.UpdateFromControl;
      FRowHeight := Height;}
    end;
    {
    with TBevel(ChildAdd(TBevel,'')) do begin
      Anchors := [akLeft, akRight];
      Width := FView.Width;
      Height := 3;
      ActiveRow.ActiveCol.UpdateFromControl;
      FRowHeight := Height;
    end;}
  finally
    ActiveLevel := l;
  end;
end;

function TViewBuilder.ChildAdd(AChildClass: TControlClass; const ACaption: string): TControl;
begin
  Result := AChildClass.Create(FOwner);
  Result.Parent := FView;
  UpdateChildFont(Result);
  SetCaption(Result, ACaption);
  if FLast <> nil then begin
    FCurLeft := FLast.Left + FLast.Width + FVMargin;
  end else begin
    FCurLeft := FVMargin;
  end;

  Result.Top := FCurTop;
  Result.Left := FCurLeft;
  FLast := Result;
  CheckViewSize;
  with ActiveRow.ColAdd do begin
    Control := Result;
  end;
end;

procedure TViewBuilder.FixSizes;
var
  i: integer;
  j: integer;

  ld: TLevelDef;
  r: TViewRow;
  l: integer;
  h, ch: integer;
  top: integer;
  c: TViewCol;

  procedure ClearLevelDefs;
  var
    i, j: integer;
    r: TViewRow;
    ld: TLevelDef;
  begin
    for i := 0 to RowCount - 1 do begin
      r := Rows[i];
      ld := LevelDefs[r.Level];
      for j := 0 to ld.ColCount - 1 do begin
        ld.Cols[j].MaxWidth := 0;
        ld.Cols[j].MaxHeight := 0;
      end;
    end;
  end;

begin
  h := 0;
  FCurTop := 0;
  ClearLevelDefs;
  FView.Height := 5;
  FView.Width := 5;{tlabel}
  for i := 0 to RowCount - 1 do begin
    r := Rows[i];

    {find rowheight, update leveldefs maximal sizes}
    for j := 0 to r.ColCount - 1 do begin
      c := r.Cols[j];
      c.UpdateFromControl;
    end;

    FRowHeight := LevelDefs[r.Level].MaxHeight;

    {makes sure the views are in the row}
    for j := 0 to r.ColCount - 1 do begin
      c := r.Cols[j];
      if (c.Control.Top < FCurTop) or
         ((c.Control.Top + c.Control.Height) > FCurTop + FRowHeight)
      then begin
        c.Control.Top := FCurTop + (FRowHeight - c.Control.Height) div 2;
      end;
    end;

    FCurTop := FCurTop + FRowHeight{+ FHMargin};
  end;

  h := 0;
  for i := 0 to RowCount - 1 do begin
    r := Rows[i];

    if r.Level > 0 then begin
      ld := LevelDefs[r.Level];
      l := 0;
      for j := 0 to ld.ColCount - 1 do begin
        if (j > 0) and (j < r.ColCount) then begin
          {if r.Cols[j].Control.Left < l then}
          r.Cols[j].Control.Left := l + ld.Cols[j].Pad.Left;
          ld.Cols[j].MaxLeft := l;
        end;
        ld.Cols[j].Justify(r.Cols[j].Control);
        l := l + ld.Cols[j].MaxWidth;
      end;
    end;

    h := h + LevelDefs[r.Level].MaxHeight;
    if LevelDefs[r.Level].MaxWidth + FVMargin > FView.Width then
        FView.Width := LevelDefs[r.Level].MaxWidth + FVMargin;
  end;
  h := h + FHMargin;
  if h > FView.Height then
    FView.Height := h;
end;

procedure TViewBuilder.Start;
var c: TControl;
begin
  FRowHeight := 0;
  FLast := nil;

  FView.Visible := false;
  while FView.ControlCount > 0 do begin
      FView.Controls[0].Free;
  end;
  FView.Width := 5;
  FView.Height := 5;
end;

procedure TViewBuilder.Stop;
begin
  FixSizes;
  FView.Parent := TWinControl(FViewOwner);
  FView.Visible := true;
end;



procedure TViewBuilder.UpdateChildFont(AChild: TControl);
var cf, f: TFont;
begin
  f := nil;
  if FView is TPanel then
    f := TPanel(FView).Font;
  if f = nil then
    exit;

  cf := nil;
  if AChild is TLabel then begin
    cf := TLabel(AChild).Font;
  end;
  if AChild is TButton then begin
    cf := TButton(AChild).Font;
  end;
  if AChild is TCheckBox then begin
    cf := TCheckBox(AChild).Font;
  end;

  if cf = nil then
    exit;
  cf.Name := f.Name;
  cf.Size := f.Size;
end;

procedure TViewBuilder.SetCaption(AChild: TControl; const ACaption: string);
var
  c: TCanvas;
  w: integer;
begin
  if AChild is TLabel then begin
    TLabel(AChild).Caption := ACaption
  end else if AChild is TButton then
    TButton(AChild).Caption := ACaption
  else if AChild is TCheckBox then
    TCheckBox(AChild).Caption := ACaption;

  if FViewOwner is TForm then begin
    c := TForm(FViewOwner).Canvas;
    w := c.TextWidth('W' + ACaption + 'W');
    {if w > AChild.Width then}
    AChild.Width := w;
  end;
end;

procedure TViewBuilder.SetActiveLevel(ALevel: integer);
begin
  if ALevel > FMaxLevel then
    FMaxLevel := ALevel;
  FActiveLevel := ALevel;
end;

function TViewBuilder.GetLevelDefs: TLevelDefs;
begin
  if FLevelDefs = nil then
    FLevelDefs := TLevelDefs.Create(nil);
  Result := FLevelDefs;
end;

destructor TViewBuilder.Destroy;
begin
  FLevelDefs.Free;
  inherited;
end;
{/TViewBuilder.}
end.
