
{                                                                              }
{ IB_WebSnap                                                                   }
{                                                                              }

{***************************************************************}
{                                                               }
{          IB Objects Components and Resources Library          }
{                                                               }
{          Copyright (C) 1996-2007 Jason Wharton                }
{          and Computer Programming Solutions, Mesa, AZ         }
{                                                               }
{  This source code unit is released under the terms of the     }
{  CPS Trustware License.                                       }
{  It may be distributed or deployed as source code or in       }
{  compiled form only in compliance with the terms and          }
{  conditions of the CPS Trustware License which may be         )
{  examined at http://www.ibobjects.com/ibo_trustware.html      )
{                                                               }
{***************************************************************}

{$INCLUDE IB_Directives.inc}

unit
  IB_WebSnap;

interface

uses
  SysUtils, Classes, Variants, HTTPApp, WebCntxt, WebAdapt, WebComp, WebForm,
  WebDisp, AdaptReq, SiteConst, SiteComp, SiteProd,
  WebSess, SessColn, WebContnrs;

// IB_WebSnapReg

type
  TIB_EndUserSessionAdapter = class(TEndUserSessionAdapter)
  private
    FOnGetDisplayName: TEndUserGetNameEvent;
  protected
    function GetDisplayNameValue: string; override;
  published
    property OnGetDisplayName: TEndUserGetNameEvent read FOnGetDisplayName
                                                    write FOnGetDisplayName;
  end;

{ Customized login stuff }

  TIB_CustomLoginFormAdapter = class;

  TIB_AdapterEmailLoginField = class(TAdapterLoginField)
  protected
    function ImplGetValue: Variant; override;
    function GetAdapter: TIB_CustomLoginFormAdapter;
  public
    property Adapter: TIB_CustomLoginFormAdapter read GetAdapter;
  published
    property FieldName;
    property DisplayLabel;
    property DisplayWidth;
  end;

  TIB_CustomLoginFormAdapter = class(TCustomLoginFormAdapter)
  private
    FOnValidateEmail: TValidateAdapterFieldEvent;
    function GetEmail: Variant;
  protected
    procedure ValidateEmail;

    { INotifyWebActivate }
    procedure ImplNotifyDeactivate; override;
    { IAdapterEditor }
    function ImplCanAddActionClass(AParent: TComponent; AClass: TClass): Boolean; override;
    { IWebDataFields }
    procedure ImplGetFieldsList(AList: TStrings); override;
    { IWebActionsList }
    procedure ImplGetActionsList(AList: TStrings); override;
  public
    property OnValidateEmail: TValidateAdapterFieldEvent read FOnValidateEmail write FOnValidateEmail;
    property Email: Variant read GetEmail;
  end;

  TIB_LoginFormAdapter = class(TIB_CustomLoginFormAdapter)
  published
    property Data;
    property Actions;
    property OnValidateEmail;
    property OnValidateUserName;
    property OnValidatePassword;
    property OnLogin;
    property PasswordRequired;
    property OnBeforeExecuteAction;
    property OnAfterExecuteAction;
    property OnBeforeGetActionResponse;
    property OnAfterGetActionResponse;
    property OnGetActionParams;
  end;

  TIB_CustomLoginFormAdapterAction = class(TLoginFormAdapterLoginAction)
  private
    function GetAdapter: TIB_CustomLoginFormAdapter;
  protected
    property Adapter: TIB_CustomLoginFormAdapter read GetAdapter;
  end;

  TIB_LoginFormAdapterLoginAction = class(TIB_CustomLoginFormAdapterAction)
  protected
    procedure ImplExecuteActionRequest(AActionRequest: IActionRequest; AActionResponse: IActionResponse); override;
    procedure ImplGetAdapterItemRequestParams(var AIdentifier: string; Params: IAdapterItemRequestParams); override;
  published
    property DisplayLabel;
    property OnBeforeExecute;
    property OnAfterExecute;
    property OnBeforeGetResponse;
    property OnAfterGetResponse;
    property OnGetParams;
  end;

{ Customized Page Adapter Stuff }

  TIB_CustomAdapterPageProducer = class;

  TIB_AdapterParameter = class(TAdapterNamedDisplayField)
  protected
    function GetAdapter: TIB_CustomAdapterPageProducer;
  public
    property Adapter: TIB_CustomAdapterPageProducer read GetAdapter;
  end;

  TIB_CustomAdapterPageProducer = class(TBaseAdapterPageProducer, IWebContent)
  private
    FHTMLDoc: TStrings;
  protected
    function GetTemplateStream(out AOwned: Boolean): TStream; override;
    function IWebContent.Content = WebContent;
    function WebContent(Options: TWebContentOptions; Layout: TLayout): string;
    procedure SetHTMLDoc(const Value: TStrings);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property HTMLDoc: TStrings read FHTMLDoc write SetHTMLDoc;
  end;

  TIB_AdapterPageProducer = class( TIB_CustomAdapterPageProducer )
  published
    property OnBeforeGetContent;
    property OnAfterGetContent;
    property Styles;
    property StylesFile;
    property WebPageItems;
    property HTMLDoc;
  end;

{------------------------------------------------------------------------------}

  TIB_CustomAdapterGrid = class(TCustomAdapterGrid)
  protected
(*
    { IGetAdapterFormLayout }
    function GetAdapterFormLayout: TAdapterFormLayout;
    { IAddFieldsEditor }
    function GetNewFieldPrefix: string;
    function GetAddFieldsItem: string;
    function GetAddAllFieldsItem: string;
    function GetAddFieldsDlgCaption: string;
    { IGetAdapterDisplayCharacteristics }
    function GetAdapterDisplayCharacteristics: TAdapterDisplayCharacteristics;
    { IWebComponentEditor }
    function ImplCanAddClass(AParent: TComponent; AClass: TClass): Boolean; override;
    { IWebContent }
    function ImplContent(Options: TWebContentOptions; ParentLayout: TLayout): string; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    property TableAttributes: TGridAttributes read FTableAttributes
      write SetTableAttributes;
    property HeadingAttributes: TGridRowAttributes read FHeadingAttributes
      write SetHeadingAttributes;
    property RowAttributes: TGridRowAttributes read FRowAttributes
      write SetRowAttributes;
*)
  end;  

  TAdapterMaskedField = class(TAdapterField)
  protected
    { IGetHTMLStyle }
    function GetInputStyleType(const AAdapterMode: string): TAdapterInputHTMLElementType; override;
  end;

const
  sLoginEmail = 'Email';    // Do not localize

{$IFNDEF IBO_VCL70_OR_GREATER}
const
  sLoginUserName = 'UserName';    // Do not localize
  sLoginPassword = 'Password';    // Do not localize
  sLoginNextPage = 'NextPage';    // Do not localize
  sLoginAction = 'Login';         // Do not localize
  sLogoutAction = 'Logout';       // Do not localize
  sLoginFormAction = 'LoginForm'; // Do not localize

  sEndUserDisplayName = 'DisplayName'; // Do not localize
  sEndUserLoggedIn = 'LoggedIn';      // Do not localize
  sEndUserLoginFormAction = 'LoginForm';// Do not localize
  sEndUserLogoutAction = 'Logout';    // Do not localize

  sApplicationTitle = 'Title';       // Do not localize
{$ENDIF}

procedure SaveSession(AID: TSessionID; aStream: TStream);
procedure RestoreSession(AID: TSessionID;  aStream: TStream);

implementation

function TAdapterMaskedField.GetInputStyleType(const AAdapterMode: string): TAdapterInputHTMLElementType;
begin
  Result := htmliPasswordInput
end;

// Write a session to a stream
procedure SaveSession(AID: TSessionID; aStream: TStream);
var
  TempSessions: TSessions;
  TempItem: TSessionItem;
begin
  TempSessions := TSessions.Create;
  try
    TempItem := TSessionItem.Create(TempSessions);
    if Sessions.GetSession(AID, TempItem) then
      TempSessions.SaveToStream(aStream)
    else
      Assert(False, 'Session not found');
  finally
    TempSessions.Free;
  end;
end;

// Update or Add all name/value pairs from the saved session to a new or existing session
procedure RestoreSession(AID: TSessionID;  aStream: TStream);
var
  TempSessions: TSessions;
  Item: TSessionItem;
  I: Integer;
begin
  if aStream.Size <> 0 then // if there is no data there, don't do anything
  begin
    TempSessions := TSessions.Create;
    try
      TempSessions.LoadFromStream(aStream);
      if AID = '' then
        // Create a new session
        AID := SessColn.Sessions.StartSession
      else
        Assert(Sessions.SessionExists(AID), 'Could not find session ' + AID);
      Item := TempSessions.Items[0] as TSessionItem;
      for I := 0 to Item.Items.Count - 1 do
        Sessions.SetItemValue(AID, Item.Items.Names[I], Item.Items.Variants[I]);
    finally
      TempSessions.Free;
    end;
  end;
end;

function TIB_EndUserSessionAdapter.GetDisplayNameValue: string;
begin
  if Assigned( OnGetDisplayName ) then
    OnGetDisplayName( Self, Result )
  else
    Result := inherited GetDisplayNameValue;
end;

{ TIB_CustomAdapterPageProducer }

constructor TIB_CustomAdapterPageProducer.Create(AOwner: TComponent);
begin
  inherited;
  FHTMLDoc := TStringList.Create;
  FHTMLDoc.Text := GetDefaultTemplate;
end;

destructor TIB_CustomAdapterPageProducer.Destroy;
begin
  inherited;
  FHTMLDoc.Free;
end;

procedure TIB_CustomAdapterPageProducer.SetHTMLDoc(const Value: TStrings);
begin
  FHTMLDoc.Assign(Value);
end;

function TIB_CustomAdapterPageProducer.GetTemplateStream(out AOwned: Boolean): TStream;
var
  S: string;
begin
  AOwned := True;
  S := HTMLDoc.Text;
  if Trim(S) = '' then
    S := GetDefaultTemplate;
  Result := TStringStream.Create(S);
end;

function TIB_CustomAdapterPageProducer.WebContent(
  Options: TWebContentOptions; Layout: TLayout): string;
var
  InStream: TStream;
  Owned: Boolean;
begin
  InStream := GetTemplateStream(Owned);
  SetWebContentOptions( Options );
  try
    Result := ContentFromStream(InStream);
  finally
    if Owned then
      InStream.Free;
    SetWebContentOptions( nil );
  end;
end;

function TIB_AdapterParameter.GetAdapter: TIB_CustomAdapterPageProducer;
begin
  if (inherited Adapter <> nil) and
     (inherited Adapter is TIB_CustomLoginFormAdapter) then
    Result := TIB_CustomAdapterPageProducer(inherited Adapter)
  else
    Result := nil;
end;

{ TIB_CustomLoginFormAdapter }

procedure TIB_CustomLoginFormAdapter.ImplGetActionsList(AList: TStrings);
begin
  AList.Clear;
  AList.AddObject('Login', TObject(TIB_LoginFormAdapterLoginAction));
end;

procedure TIB_CustomLoginFormAdapter.ImplNotifyDeactivate;
begin
  inherited;
  NextPage := '';
end;

procedure TIB_CustomLoginFormAdapter.ImplGetFieldsList(AList: TStrings);
begin
  AList.Clear;
  AList.AddObject( sLoginUserName, TObject(TAdapterUserNameField));
  AList.AddObject( sLoginPassword, TObject(TAdapterPasswordField));
//!!!  AList.AddObject( sLoginEmail,    TObject(TIB_AdapterEmailLoginField));
end;

function TIB_CustomLoginFormAdapter.ImplCanAddActionClass(
  AParent: TComponent; AClass: TClass): Boolean;
begin
  Result :=
    inherited ImplCanAddActionClass(AParent, AClass) or
    AClass.InheritsFrom(TIB_CustomLoginFormAdapterAction);
end;

procedure TIB_CustomLoginFormAdapter.ValidateEmail;
var
  Handled: Boolean;
begin
  if Assigned(OnValidateEmail) then
    OnValidateEmail(Self, Email, Handled)
  else
    Handled := False;
end;

function TIB_CustomLoginFormAdapter.GetEmail: Variant;
var
  FieldValue: IActionFieldValue;
  FieldValues: IActionFieldValues;
begin
  Result := Unassigned;
  if Supports(WebContext.AdapterRequest, IActionFieldValues, FieldValues) then
  begin
    FieldValue := FieldValues.ValueOfField(sLoginEmail);
    if FieldValue <> nil then
      Result := FieldValue.Values[0];
  end;
end;

{ TIB_LoginFormAdapterLoginAction }

const
  sNextPage = 'NextPage'; // Do not localize

procedure TIB_LoginFormAdapterLoginAction.ImplGetAdapterItemRequestParams(
  var AIdentifier: string; Params: IAdapterItemRequestParams);
var
  Page: string;
begin
  Page := NextPage;
  if Page = '' then
    Page := WebContext.RequestedPageName;
  if Page <> '' then
    Params.ParamValues.Add(Format('%s=%s', [sNextPage, Page]));
  inherited ImplGetAdapterItemRequestParams(AIdentifier, Params);
end;

function GetAdapterRequestParamsIntf(AAdapterRequest: IUnknown): IAdapterRequestParams;
begin
  if not Supports(AAdapterRequest, IAdapterRequestParams, Result) then
    Assert(False);
end;

function GetActionRespondWith(AActionResponse: IActionResponse): IActionRespondWith;
begin
  if not Supports(AActionResponse, IActionRespondWith, Result) then
    Assert(False);
end;

procedure TIB_LoginFormAdapterLoginAction.ImplExecuteActionRequest(
  AActionRequest: IActionRequest; AActionResponse: IActionResponse);
var
  UserID: Variant;
  S: TStrings;
begin
  RedirectOptions := roRedirect;
  if (Adapter <> nil) then
    with Adapter do
    begin
      try
        NextPage := GetAdapterRequestParamsIntf(AActionRequest).ValueOfParam(sNextPage);
        UpdateRecords;
        ValidateUserName;
        ValidatePassword;
        ValidateEmail;
        if UserList <> nil then
        begin
          S := TStringList.Create;
          try
            S.Add(Format('UserName=%s', [Adapter.UserName])); // Do not localize
            S.Add(Format('Password=%s', [Adapter.Password])); // Do not localize
            S.Add(Format('Email=%s',    [Adapter.Email]));    // Do not localize
            UserID := UserList.ValidateUser(S);
          finally
            S.Free;
          end;
        end
        else
          UserID := UserName;
        ExecuteLogin(UserID);
        if NextPage <> '' then
          with GetActionRespondWith(AActionResponse) do
          begin
            RespondWithPage(NextPage);
          end;
      except
        on E: Exception do
        begin
          Errors.AddError(E);
        end;
      end;
    end;
  if Errors.ErrorCount > 0 then
     Adapter.EchoActionFieldValues := True;
end;

function TIB_AdapterEmailLoginField.ImplGetValue: Variant;
begin
  Result := '';
end;

function TIB_AdapterEmailLoginField.GetAdapter: TIB_CustomLoginFormAdapter;
begin
  if (inherited Adapter <> nil) and
     (inherited Adapter.InheritsFrom( TIB_CustomLoginFormAdapter )) then
    Result := TIB_CustomLoginFormAdapter(inherited Adapter)
  else
  begin
    Result := nil;
    Assert(False); // LoginFormAdapter not found
  end;
end;

function TIB_CustomLoginFormAdapterAction.GetAdapter: TIB_CustomLoginFormAdapter;
begin
  if (inherited Adapter <> nil) and (inherited Adapter is TIB_CustomLoginFormAdapter) then
    Result := TIB_CustomLoginFormAdapter(inherited Adapter)
  else
  begin
    Result := nil;
    Assert(False); // LoginFormAdapter not found
  end;
end;

{------------------------------------------------------------------------------}

(*
{ TCustomAdapterGrid }

function TCustomAdapterGrid.ImplCanAddClass(AParent: TComponent;
  AClass: TClass): Boolean;
begin
  Result :=
    AClass.InheritsFrom(TCustomAdapterEditColumn) or
    AClass.InheritsFrom(TCustomAdapterDisplayColumn) or
    AClass.InheritsFrom(TCustomAdapterCommandColumn) or
    CanAddClassHelper(Self, AParent, AClass);
end;

type
  TGridLayout = class(TAdapterFormLayout)
  protected
    function ImplLayoutButton(const HTMLButton: string; Attributes: TLayoutAttributes): string; override;
    function ImplLayoutField(const HTMLField: string; Attributes: TLayoutAttributes): string; override;
    function ImplLayoutLabelAndField(const HTMLLabel, HTMLField: string; Attributes: TLayoutAttributes): string; override;
    function ImplLayoutTable(const HTMLTable: string; Attributes: TLayoutAttributes): string; override;
  end;

function TGridLayout.ImplLayoutTable(const HTMLTable: string;
  Attributes: TLayoutAttributes): string;
begin
  // Nested table
  Result := ImplLayoutField(HTMLTable, Attributes);
end;

function TGridLayout.ImplLayoutButton(const HTMLButton: string; Attributes: TLayoutAttributes): string;
begin
  Result := ImplLayoutField(HTMLButton, Attributes);
end;

function TGridLayout.ImplLayoutField(const HTMLField: string; Attributes: TLayoutAttributes): string;
begin
  Result := HTMLField;
end;

function TGridLayout.ImplLayoutLabelAndField(const HTMLLabel, HTMLField: string; Attributes: TLayoutAttributes): string;
begin
  // Label ignored
  Result := ImplLayoutField(HTMLField, Attributes);
end;

const
  sSetAdapterMode = '<%%'#13#10 +
                     '{'#13#10 +                            // Do not localize
                     '   var lastMode=%0:s.Mode'#13#10 +         // Do not localize
                     '   if(lastMode=="")'#13#10 +     // Do not localize
                     '   {'#13#10 +         // Do not localize
                     '     %0:s.Mode=''%1:s'''#13#10 +          // Do not localize
                     '   } %%>'#13#10 +                          // Do not localize
                     '%2:s'#13#10 +                           // Do not localize
                    '<%% %0:s.Mode=lastMode'#13#10 +         // Do not localize
                     '}  %%>'#13#10;                           // Do not localize


function TCustomAdapterGrid.ImplContent(
  Options: TWebContentOptions; ParentLayout: TLayout): string;
var
  Intf: ILayoutWebContent;
  A: TLayoutAttributes;
begin
  FTableLayout := TGridLayout.Create(ParentLayout);
  try
    Result := FormatTable(FTableLayout, Options);
  finally
    FreeAndNil(FTableLayout);
  end;
  if Supports(ParentLayout, ILayoutWebContent, Intf) then
    if ParentLayout is TAdapterFormLayout then
    begin
      Result := Result + ChildTableIndent(GetParentComponent) + '</table>'#13#10;  { do not localize }
      Result := Intf.LayoutTable(ChildTableIndent(GetParentComponent) + Result, GetLayoutAttributes)
    end
    else
    begin
      Result := Result + '</table>'#13#10;  { do not localize }
      A := TLayoutAttributes.Create;
      try
        with A do
        begin
          AddQuotedAttrib(ControlAttributes, sAlignAttr, HTMLAlignValues[haCenter]);
          AddQuotedAttrib(ControlAttributes, sVAlignAttr, HTMLVAlignValues[haTop]);
          // Work better with a layout group
          Result := Intf.LayoutTable(#13#10 + Result, A);
        end
      finally
        A.Free;
      end;
    end
  else
    Result := Result + '</table>'#13#10;  { do not localize }

  if (Adapter <> nil) and (AdapterMode <> '') then
    Result := Format(sSetAdapterMode,
                     [MakeAdapterVariableName(Self), AdapterMode, Result]);
end;

const
  // Params 0 - AdapterName, 1 - MemberName, 2 - Indent
  sWriteHiddenFields =
  '<%%%2:s if (%0:s.%1:s != null)'#13#10 +                                      { do not localize }
   '%2:s   {'#13#10 +                                                           { do not localize }
   '%2:s     %0:s.%1:s.WriteFields(Response)'#13#10 +                           { do not localize }
   '%2:s   } %%>'#13#10;                                                        { do not localize }
  sHiddenFields = 'HiddenFields';                                               { do not localize }
  sHiddenRecordFields = 'HiddenRecordFields';                                   { do not localize }
  // Params 0 - AdapterName, 1 - Row
  sJSGridForEach =
  '<%% {'#13#10 +                                                               { do not localize }
   '   var ri = 0'#13#10 +                                                      { do not localize }
   '   var e = new Enumerator(%0:s.Records)'#13#10 +                            { do not localize }
   '   for (; !e.atEnd(); e.moveNext())'#13#10 +                                { do not localize }
   '   { %%>'#13#10 +                                                           { do not localize }
   '%1:s' +                                                                     { do not localize }
  '<%%  ri++'#13#10 +                                                           { do not localize }
   '   }'#13#10 +                                                               { do not localize }
   '   e.moveFirst()'#13#10 +                                                   { do not localize }
   '   } %%>'#13#10;                                                            { do not localize }

function TCustomAdapterGrid.FormatTable(Layout: TLayoutWebContent;
  Options: TWebContentOptions): string;

  function TableHeader: string;
  var
    Attribs: string;
  begin
    with TableAttributes do
    begin
      Attribs := Attribs + HTMLAlign[Align];
      AddIntAttrib(Attribs, sCellSpacingAttr, CellSpacing);
      AddIntAttrib(Attribs, sCellPaddingAttr, CellPadding);
      AddIntAttrib(Attribs, sBorderAttr, Border);
      AddStringAttrib(Attribs, sBgColorAttr, BgColor);
      AddQuotedAttrib(Attribs, sStyleAttr, Style);
      AddQuotedAttrib(Attribs, sClassAttr, StyleRule);
      AddCustomAttrib(Attribs, Custom);
    end;
    Result := Format('<table%s>'#13#10, [Attribs]);   { do not localize }
  end;

  function RowHeader(HeadingAttributes: TGridRowAttributes): string;
  var
    Attribs: string;
  begin
    with HeadingAttributes do
    begin
      Attribs := Attribs + HTMLAlign[Align];
      Attribs := Attribs + HTMLVAlign[VAlign];
      AddQuotedAttrib(Attribs, sBgColorAttr, BgColor);
      AddQuotedAttrib(Attribs, sStyleAttr, Style);
      AddQuotedAttrib(Attribs, sClassAttr, StyleRule);
      AddCustomAttrib(Attribs, Custom);
    end;
    Result := FieldIndent(Self) + Format('<tr%s>'#13#10, [Attribs]);  { do not localize }
  end;

  function FormatHiddenFields(const AdapterVarName: string): string;
  begin
    if AdapterVarName <> '' then
    begin
      Result := Format(sWriteHiddenFields, [AdapterVarName, sHiddenFields, '' { Indent }]);
    end;
  end;

  function FormatHiddenRecordFields(const AdapterVarName: string): string;
  begin
    if AdapterVarName <> '' then
    begin
      Result := Format(sWriteHiddenFields, [AdapterVarName, sHiddenRecordFields, '  ' { Indent }]);
    end;
  end;

var
  I: Integer;
  OneRow: string;
  Field: TComponent;
  FormatColumn: IFormatColumn;
  WebContent: IWebContent;
  VarName: string;
  FieldContent: string;
begin
  Result := TableHeader + RowHeader(HeadingAttributes);
  for I := 0 to VisibleFields.Count - 1 do
  begin
    Field := VisibleFields[I];
    if Supports(IInterface(Field), IWebContent, WebContent) then
      FieldContent := WebContent.Content(Options, Layout)
    else
      FieldContent := '';
    if Supports(IInterface(Field), IFormatColumn, FormatColumn) then
    begin
      Result := Result + FormatColumn.FormatColumnHeading(Options);
      OneRow := OneRow + FormatColumn.FormatColumnData(FieldContent, Options);
    end
    else if Field is TWebDataDisplay then
    begin
      // Default formatting
      Result := Result + FieldIndent(Self) + FormatColumnHeading(TWebDataDisplay(Field));
      OneRow := OneRow + FieldIndent(Self) + FormatColumnData(TWebDataDisplay(Field), FieldContent);
    end
    else
      Assert(False, 'Do not know how to format column');
  end;
  Result := Result + FieldIndent(Self) + '</tr>'#13#10;  { do not localize }

  VarName := MakeAdapterVariableName(Self);
  OneRow := OneRow + FieldIndent(Self) + '</tr>'#13#10 + FormatHiddenRecordFields(VarName);  { do not localize }
  if VarName <> '' then
    Result := Result + Format(sJSGridForEach,
      [VarName, Format('%s%s', [RowHeader(RowAttributes), OneRow])])
  else
    Result := Result + Format('%s%s', [RowHeader(RowAttributes), OneRow]);
  Result := Result + FormatHiddenFields(VarName);
end;

procedure TCustomAdapterGrid.SetHeadingAttributes(
  const Value: TGridRowAttributes);
begin
  FHeadingAttributes.Assign(Value);
end;

procedure TCustomAdapterGrid.SetRowAttributes(
  const Value: TGridRowAttributes);
begin
  FRowAttributes.Assign(Value);
end;

procedure TCustomAdapterGrid.SetTableAttributes(
  const Value: TGridAttributes);
begin
  FTableAttributes.Assign(Value);
end;

constructor TCustomAdapterGrid.Create(AOwner: TComponent);
begin
  inherited;
  FTableAttributes := TGridAttributes.Create(Self);
  FHeadingAttributes := TGridRowAttributes.Create(Self);
  FRowAttributes := TGridRowAttributes.Create(Self);
  FTableAttributes.Border := 1;
end;

destructor TCustomAdapterGrid.Destroy;
begin
  inherited;
  FTableAttributes.Free;
  FHeadingAttributes.Free;
  FRowAttributes.Free;
end;

*)

end.
