{
    This file is part of the Free Component Library (FCL)
    Copyright (c) 1999-2000 by Michael Van Canneyt and Florian Klaempfl

    See the file COPYING.FPC, included in this distribution,
    for details about the copyright.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

 **********************************************************************}

unit fpobserver;

{$mode objfpc}{$H+}
{$interfaces corba}

interface

uses
  Classes, SysUtils, typinfo, contnrs;

Type

  TObservedHook = Class(TObject,IFPObserved)
  Protected
    FObservers : TFPList;
    FSender : TObject;
  Public
    // ASender will be the default sender.
    Constructor CreateSender(ASender : TObject);
    Destructor Destroy; override;
    Procedure FPOAttachObserver(AObserver : TObject);
    Procedure FPODetachObserver(AObserver : TObject);
    Procedure Changed;
    Procedure AddItem(AItem : TObject);
    Procedure DeleteItem(AItem : TObject);
    Procedure CustomNotify(Data : Pointer = Nil);
    Procedure FPONotifyObservers(ASender : TObject; AOperation : TFPObservedOperation; Data : Pointer);
    Property Sender : TObject Read FSender;
  end;

//  EObserver = Class(Exception);


  { TBaseMediator }

  TMediatingEvent = Procedure(Sender : TObject; var Handled : Boolean) of object;

  TBaseMediator = Class(TComponent,IFPObserver)
  private
    FActive: Boolean;
    FOnObjectToView: TMediatingEvent;
    FOnViewToObject: TMediatingEvent;
    FReadOnly: Boolean;
    FTransferring : Boolean;
    FSubjectPropertyName: String;
    FSubject: TObject;
    FValueList: TObjectList;
    FViewPropertyName: String;
    procedure SetReadOnly(const AValue: Boolean);
    procedure SetValueList(const AValue: TObjectList);
    procedure SetViewPropertyName(const AValue: String); Virtual;
  Protected
    // Should return true (Default) if ViewPropertyName is published
    Class Function PublishedViewProperty : Boolean; virtual;
    // Should return true (Default) if SubjectPropertyName is published
    Class Function PublishedSubjectProperty : Boolean; virtual;
    // Set active. Descendents (such as list mediators) can override this.
    procedure SetActive(const AValue: Boolean); virtual;
    // set subject. Attaches observer and calls MaybeObjectToView
    procedure SetSubject(const AValue: TObject); virtual;
    // set subjectpropertyname. Checks if it exists, and calls MaybeObjectToView
    procedure SetSubjectPropertyName(const AValue: String); virtual;
    // Can be used in descendents to respond to onchange events
    Procedure ViewChangedHandler(Sender : TObject);  virtual;
    // Check if APropertyName is published property of AObject.
    // Only performed if both parameters are not empty.
    procedure CheckPropertyName(AObject: TObject; const APropertyName: String);
    // If all CheckObjectSubject and Active are true, call ObjectToView.
    Procedure MaybeObjectToView;
    // If all CheckObjectSubject and Active are true, call ViewToObject.
    Procedure MaybeViewToObject;
    // Check if Subject/View and property names are set up correctly.
    Function  CheckViewSubject : Boolean;
    // Override  next two for custom behaviour.
    // Copies Subject.SubjectPropertyName to View.ViewPropertyName.
    Procedure DoObjectToView; virtual;
    // Copies View.ViewPropertyName to Subject.SubjectPropertyName
    Procedure DoViewToObject; virtual;
    // Override these, and call inherited at the end.
    // Get View component. Typically a TCustomEdit instance.
    function  GetView : TObject; virtual;
    // Descendents should call this when the view changed.
    procedure ViewChanged; virtual;
    // Descendents should override this to handle changes in the value list
    procedure ValuelistChanged; virtual;
    // IFPObserver. Will call the necessary events.
    Procedure FPOObservedChanged(ASender : TObject; Operation : TFPObservedOperation; Data : Pointer);
    // Raise an error which shows more information about the control, subject and fieldname.
    Procedure RaiseMediatorError(Const Msg : String); overload;
    // Format version
    Procedure RaiseMediatorError(Const Fmt : String; Args : Array of const); overload;
    // View property that will be set by default
    Property ViewPropertyName : String Read FViewPropertyName Write SetViewPropertyName;
    // Is a copy operation View <-> Subject in progress ?
    Property Transferring : Boolean Read FTransferring;
  Public
    Destructor Destroy; override;
    // Copy subject to view. No check is done to see if all is well.
    Procedure ObjectToView;
    // Copy view to subject. No check is done to see if all is well.
    Procedure ViewToObject;
    // Minimum class that View must have to be handled by this mediator.
    class function ViewClass: TClass; virtual;
    // Composite mediator or not ?
    class function CompositeMediator : Boolean; virtual;
    // Subject. Must have IFPObserved interface
    Property Subject : TObject Read FSubject Write SetSubject;
    // View. Must have ViewPropertyName, if in use.
    Property View : TObject Read GetView;
    // Value list. To be used in mediators that use a dynamical value list
    // such as Listbox, combobox, groupbox.
    Property Valuelist : TObjectList Read FValueList Write SetValueList;
  Published
    // Property that will be copied to view.
    Property SubjectPropertyName : String Read FSubjectPropertyName Write SetSubjectPropertyName;
    // If not active, no copying is being done either way.
    Property Active : Boolean Read FActive Write SetActive;
    // If ReadOnly, only ObjectToView is used
    Property ReadOnly : Boolean Read FReadOnly Write SetReadOnly;
    // Can be used to copy data from control (view) to subject manually
    Property OnViewToObject : TMediatingEvent Read FOnViewToObject Write FOnViewToObject;
    // Can be used to copy data from control (view) to subject manually
    Property OnObjectToView : TMediatingEvent Read FOnObjectToView Write FOnObjectToView;
  end;
  TMediatorClass = Class of TBaseMediator;

  // Forward definitions
  TBaseListMediator = Class;

  { TComponentMediator }
  { General-purpose of Mediating views. Can be used on any form/component }

  TComponentMediator = Class(TBaseMediator)
    FViewComponent : TComponent;
  Protected
    function  GetView : TObject; override;
    procedure SetComponent(const AValue: TComponent);
  Public
    procedure Notification(AComponent: TComponent;  Operation: TOperation); override;
  Published
    // General component which can be set in Object Inspector
    Property ViewComponent : TComponent Read FViewComponent Write SetComponent;
    // Punlish property so it can be set in Object Inspector
    Property ViewPropertyName;
  end;

  { Event object used for OnBeforeSetupField event. Is used to allow formatting
    of fields before written to listview Caption or Items. }
  TOnBeforeSetupField = procedure(AObject: TObject; const AFieldName: string; var AValue: string) of object;

  { TListItemMediator }

  TListItemMediator = class(TObject, IFPObserver)
  private
    FSubject: TObject;
    FOnBeforeSetupField: TOnBeforeSetupField;
    FListMediator : TBaseListMediator;
    Function GetActive : Boolean;
  protected
    procedure SetSubject(const AValue: TObject); virtual;
    Procedure FPOObservedChanged(ASender : TObject; Operation : TFPObservedOperation; Data : Pointer); virtual;
    Procedure ObjectToView; virtual;
    Procedure ViewToObject; virtual;
  public
    destructor Destroy; override;
    procedure MaybeObjectToView;
    property OnBeforeSetupField: TOnBeforeSetupField read FOnBeforeSetupField write FOnBeforeSetupField;
    property Subject : TObject read FSubject write SetSubject;
    property Active : Boolean read GetActive;
  end;

  { TBaseListMediator - Base mediator that handles lists of objects.

    Needs a TList as subject. Items in list must have IFPObserved
    interface. It will create one (and use as subject) if passed a normal
    list or a collection.
  }

  TBaseListMediator = class(TBaseMediator)
  private
    FOnBeforeSetupField: TOnBeforeSetupField;
    FMediatorList: TFPObjectList;
    FListChanged : Boolean;
    procedure SetOnBeforeSetupField(const Value: TOnBeforeSetupField);
  protected
    // This needs to return false
    Class Function PublishedViewProperty : Boolean; override;
    // Descendents can override;
    Function AddObject(AObject: TObject; AIndex: Integer) : TListItemMediator; virtual;
    // Set all descendents to active
    procedure SetActive(const AValue: Boolean); override;
    // Must be overridden in descendents, and should return selected object
    function GetSelectedObject: TObject; virtual;
    // Must be overridden in descendents, and should set selected object
    procedure SetSelectedObject(const AValue: TObject); virtual;
    // Must be overridden in descendents to create an item mediator and add it to GUI control
    // Subject will be set after this call.
    Function CreateItemMediator(AData: TObject; ARow : integer) : TListItemMediator; virtual; abstract;
    // This frees the mediator. Descendents can override to additionally update the GUI control
    procedure DoDeleteItemMediator(AIndex : Integer; AMediator : TListItemMediator); virtual;
    // Creates a mediator for all items in the list. List Item Mediators are re-used (subject is set)
    procedure CreateSubMediators; virtual;
    // Does nothing
    procedure DoViewToObject; override;
    // Calls CreateSubMediators. Override for additional GUI setup.
    procedure DoObjectToView; override;
    // Additional checks on subject.
    procedure SetSubject(const AValue: TObject); override;

    Function FindObjectMediator(AObject : TObject; out AtIndex : Integer) : TListItemMediator;
    property MediatorList: TFPObjectList read FMediatorList;
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
    class function CompositeMediator: Boolean; override;
    // This should handle additional additem/deleteitem events
    Procedure ObservedChanged(ASender : TObject; Operation : TFPObservedOperation);
    // Selected item in the list.
    property SelectedObject: TObject read GetSelectedObject write SetSelectedObject;
  published
    // Event to setup fields in item mediators.
    property OnBeforeSetupField: TOnBeforeSetupField read FOnBeforeSetupField write SetOnBeforeSetupField;
  end;

  { TMediatorFieldInfo - Describe a column in a columnar list display }

  TMediatorFieldInfo = class(TCollectionItem)
  private
    FWidth: integer;
    FCaption: string;
    FPropName: string;
    FAlign: TAlignment;
    function GetCaption: string;
    procedure SetAlign(const AValue: TAlignment);
    procedure SetCaption(const AValue: string);
    procedure SetPropName(const AValue: string);
    procedure SetWidth(const AValue: Integer);
  protected
    function GetAsString: string; virtual;
    procedure SetAsString(const AValue: string); virtual;
    Procedure Change;
  public
    procedure Assign(Source: TPersistent); override;
    // Setting this will parse everything.
    property AsString: string read GetAsString write SetAsString;
  published
    // Property Caption to be used for column head.
    property Caption: string read GetCaption write SetCaption;
    // Property Name to be displayed in column
    property PropertyName: string read FPropName write SetPropName;
    // Width of column
    property Width: Integer read FWidth write SetWidth;
    // Alignment of column
    property Alignment: TAlignment read FAlign write SetAlign default taLeftJustify;
  end;

  TColumnsListMediator = Class;

  { TMediatorFieldInfoList - Collection describing the columns in a columnar list display }

  TMediatorFieldInfoList = class(TCollection)
  private
    FMediator : TColumnsListMediator;
    function GetAsString: string;
    function GetI(Index: integer): TMediatorFieldInfo;
    procedure SetI(Index: integer; const AValue: TMediatorFieldInfo);
  protected
    procedure Notify(Item: TCollectionItem;Action: TCollectionNotification); override;
    Property Mediator : TColumnsListMediator read FMediator;
  public
    // Adding items to the collection.
    function AddFieldInfo: TMediatorFieldInfo; overload;
    function AddFieldInfo (Const APropName : String; AFieldWidth : Integer) : TMediatorFieldInfo; overload;
    function AddFieldInfo (Const APropName,ACaption : String; AFieldWidth : Integer) : TMediatorFieldInfo; overload;
    function AddFieldInfo (Const APropName,ACaption : String; AFieldWidth : Integer; AAlignment : TAlignment) : TMediatorFieldInfo; overload;
    property FieldInfo[Index: integer]: TMediatorFieldInfo read GetI write SetI; default;
    property AsString: string read GetAsString;
  end;

  { TColumnsListItemMediator - List item mediator that can handle multiple columns }

  TColumnsListItemMediator = class(TListItemMediator)
  Private
    Function GetFieldsInfo: TMediatorFieldInfoList;
  Published
    property FieldsInfo: TMediatorFieldInfoList read GetFieldsInfo;
  end;

  { TColumnsListMediator - List mediator that handles multiple columns }

  TColumnsListMediator = class(TBaseListMediator)
  Private
    FFieldsInfo: TMediatorFieldInfoList;
    procedure SetFieldsInfo(const AValue: TMediatorFieldInfoList);
    function GetDisplayNames: string;
    procedure SetDisplayNames(const AValue: string);
    procedure FieldInfoChanged(Item: TMediatorFieldInfo; Action: TCollectionNotification); virtual;
  Protected
    Class Function PublishedSubjectProperty : Boolean; override;
    procedure ParseDisplayNames(const AValue: string);
    // Called by DoViewToObject prior to creating item mediators
    procedure CreateColumns; virtual;
    // Calls CreateColumns and CreateSubMediators. Override for additional GUI setup.
    procedure DoObjectToView; override;
  Public
    Constructor Create(AOwner: TComponent); override;
    Destructor Destroy; override;
    // Can be used to set the column properties in 1 statement.
    property DisplayNames: string read GetDisplayNames write SetDisplayNames;
  Published
    // How to display the columns in the list.
    property FieldsInfo: TMediatorFieldInfoList read FFieldsInfo write SetFieldsInfo;
  end;

  { TMediatorDef - Mediator Definition Storage for MediatorManager }

  TMediatorDef = class(TCollectionItem)
  private
    FMC: TMediatorClass;
    FMSC: TClass;
    FPN: string;
    FPT: TTypeKinds;
  public
    // Return True if this definition handles the Subject,Gui,APropinfo trio
    function Handles(ASubject: TObject; AGui: TComponent; APropInfo: PPropInfo): Boolean;
    // Return True if this definition matches 'closer' than M.
    // Note that both current and M must have Handles() returned true for this to be useful.
    function BetterMatch(M: TMediatorDef): Boolean;
    // Definition
    property MediatorClass: TMediatorClass read FMC write FMC;
    property MinSubjectClass: TClass read FMSC write FMSC;
    property PropertyTypes: TTypeKinds read FPT write FPT;
    property PropertyName: string read FPN write FPN;
  end;


  TMediatorDefs = class(TCollection)
  private
    function GetDef(Index: integer): TMediatorDef;
    procedure SetDef(Index: integer; const AValue: TMediatorDef);
  public
    function AddDef: TMediatorDef;
    property Defs[Index: integer]: TMediatorDef read GetDef write SetDef; default;
  end;


  TMediatorManager = class(TObject)
  private
    FDefs: TMediatorDefs;
  public
    constructor Create;
    destructor Destroy; override;
    // If APropName is empty or APropInfo is Nil, a composite mediator will be searched.
    function FindDefFor(ASubject: TObject; AGui: TComponent): TMediatorDef; overload;
    function FindDefFor(ASubject: TObject; AGui: TComponent; const APropName: string): TMediatorDef; overload;
    function FindDefFor(ASubject: TObject; AGui: TComponent; APropInfo: PPropInfo): TMediatorDef; overload;
    function RegisterMediator(MediatorClass: TMediatorClass; MinSubjectClass: TClass): TMediatorDef; overload;
    function RegisterMediator(MediatorClass: TMediatorClass; MinSubjectClass: TClass; PropertyName: string): TMediatorDef; overload;
    function RegisterMediator(MediatorClass: TMediatorClass; MinSubjectClass: TClass; PropertyTypes: TTypeKinds): TMediatorDef; overload;
    property Defs: TMediatorDefs read FDefs;
  end;

  EMediator = class(Exception);

function MediatorManager: TMediatorManager;
Procedure MediatorError(Sender : TObject; Const Msg : String); overload;
Procedure MediatorError(Sender : TObject; Const Fmt : String; Args : Array of const); overload;

implementation


Resourcestring
  SErrNotObserver = 'Instance of class %s is not an observer.';
  SErrInvalidPropertyName = '%s is not a valid published property of class %s';
  SErrObjectCannotBeObserved = 'Cannot observe an instance of class %d';
  sErrInvalidFieldName      = 'No fieldname specified for column %d';
  sErrInvalidAlignmentChar  = 'Invalid alignment character "%s" specified for column %d';
  sErrInvalidWidthSpecifier = 'Invalid with "%s" specified for column %d';
  sErrNotListObject         = '%s is not a TObjectList';
  sErrCompositeNeedsList    = '%s needs a TObjectList class but is registered with %s';
  SErrActive                = 'Operation not allowed while the mediator is active';
  SErrNoGuiFieldName        = 'no gui fieldname set';
  SErrNoSubjectFieldName    = 'no subject fieldname set';

{ ---------------------------------------------------------------------
  Mediator global routines
  ---------------------------------------------------------------------}

Procedure MediatorError(Sender : TObject; Const Msg : String); overload;

Var
  M : TBaseMediator;
  C : TComponent;
  V,S : TObject;
  CN,SN,Err : String;

begin
  if (Sender=Nil) then
    Err:=Msg
  else If Sender is TBaseMediator then
    begin
    M:=TBaseMediator(Sender);
    V:=M.View;
    S:=M.Subject;
    CN:='';
    If Assigned(V) then
      begin
      if (V is TComponent) then
        begin
        C:=TComponent(V);
        CN:=C.Name;
        end;
      If (CN='') then
        CN:=C.ClassName+' instance';
      end
    else
      CN:='Nil';
    If Assigned(S) then
      SN:=S.ClassName
    else
      SN:='Nil';
    Err:=Format('Mediator %s (%s,%s,%s) : %s',[M.ClassName,SN,CN,M.SubjectPropertyName,Msg]);
    end
  else if (Sender is TComponent) and (TComponent(Sender).Name<>'') then
    Err:=Format('%s : %s',[TComponent(Sender).Name,Msg])
  else
    Err:=Format('%s : %s',[Sender.ClassName,Msg]);
  Raise EMediator.Create(Err);
end;

Procedure MediatorError(Sender : TObject; const Fmt : String; Args : Array of const); overload;

begin
  MediatorError(Sender,Format(Fmt,Args));
end;

Var
  MM : TMediatorManager;

function MediatorManager: TMediatorManager;
begin
  if (MM = nil) then
    MM := TMediatorManager.Create;
  Result := MM;
end;

{ TObservedHook }

constructor TObservedHook.CreateSender(ASender: TObject);
begin
  FSender:=ASender;
  If FSender=Nil then
    FSender:=Self;
end;

destructor TObservedHook.Destroy;
begin
  If Assigned(FObservers) then
    begin
    FPONotifyObservers(FSender,ooFree,Nil);
    FreeAndNil(FObservers);
    end;
  inherited Destroy;
end;

procedure TObservedHook.FPOAttachObserver(AObserver: TObject);

Var
  I : IFPObserver;

begin
  If Not AObserver.GetInterface(SGUIDObserver,I) then
    Raise EObserver.CreateFmt(SErrNotObserver,[AObserver.ClassName]);
  If not Assigned(FObservers) then
    FObservers:=TFPList.Create;
  FObservers.Add(I);
end;

procedure TObservedHook.FPODetachObserver(AObserver: TObject);

Var
  I : IFPObserver;

begin
  If Not AObserver.GetInterface(SGUIDObserver,I) then
    Raise EObserver.CreateFmt(SErrNotObserver,[AObserver.ClassName]);
  If Assigned(FObservers) then
    begin
    FObservers.Remove(I);
    If (FObservers.Count=0) then
      FreeAndNil(FObservers);
    end;
end;

procedure TObservedHook.Changed;
begin
  FPONotifyObservers(Sender,ooChange,Nil)
end;

procedure TObservedHook.AddItem(AItem: TObject);
begin
  FPONotifyObservers(FSender,ooAddItem,AItem);
end;

procedure TObservedHook.DeleteItem(AItem: TObject);
begin
  FPONotifyObservers(FSender,ooDeleteItem,AItem);
end;

procedure TObservedHook.CustomNotify(Data : Pointer = Nil);
begin
  FPONotifyObservers(FSender,ooCustom,Data);
end;

procedure TObservedHook.FPONotifyObservers(ASender: TObject;  AOperation: TFPObservedOperation; Data : Pointer);

Var
  O : TObject;
  I : Integer;
  Obs : IFPObserver;

begin
  If Assigned(FObservers) then
    For I:=FObservers.Count-1 downto 0 do
      begin
      Obs:=IFPObserver(FObservers[i]);
      Obs.FPOObservedChanged(ASender,AOperation,Data);
      end;
end;

{ TBaseMediator }

function TBaseMediator.GetView: TObject;
begin
  Result:=Nil;
end;

procedure TBaseMediator.ViewChanged;
begin
  If PublishedViewProperty then
    CheckPropertyName(View,ViewPropertyName);
  MaybeObjectToView
end;

procedure TBaseMediator.ValuelistChanged;
begin
  // Do nothing
end;

procedure TBaseMediator.SetActive(const AValue: Boolean);
begin
  if FActive=AValue then exit;
  FActive:=AValue;
  MaybeObjectToView;
end;

procedure TBaseMediator.SetReadOnly(const AValue: Boolean);
begin
  if FReadOnly=AValue then exit;
  FReadOnly:=AValue;
  MaybeObjectToView;
end;

procedure TBaseMediator.SetValueList(const AValue: TObjectList);

Var
  I : IFPObserved;

begin
  if FValueList=AValue then exit;
  If FValueList<>Nil then
      I.FPODetachObserver(Self);
  If Assigned(AValue) then
    begin
    FValueList:=AValue;
    If Assigned(AValue) then
      AValue.FPOAttachObserver(Self);
    end;
  FValueList:=AValue;
  ValueListChanged;
end;

procedure TBaseMediator.CheckPropertyName(AObject : TObject; const APropertyName : String);

begin
  If Assigned(AObject) and (APropertyName<>'') then
    If Not IsPublishedProp(AObject,APropertyName) then
      Raise EObserver.CreateFmt(SErrInvalidPropertyName,[APropertyName,AObject.ClassName]);
end;

procedure TBaseMediator.MaybeObjectToView;
begin
  If FActive and CheckViewSubject then
    ObjectToView
end;

procedure TBaseMediator.MaybeViewToObject;
begin
  If FActive and (Not ReadOnly) and CheckViewSubject then
    ViewToObject;
end;

function TBaseMediator.CheckViewSubject: Boolean;

Var
  O : TObject;

begin
  O:=GetView;
  Result:=Assigned(FSubject)
          and Assigned(O)
          and (ViewPropertyName<>'')
          and (SubjectPropertyName<>'');
end;

procedure TBaseMediator.SetSubjectPropertyName(const AValue: String);

begin
  if FSubjectPropertyName=AValue then exit;
  If PublishedSubjectProperty then
    CheckPropertyName(FSubject,AValue);
  FSubjectPropertyName:=AValue;
  MaybeObjectToView;
end;


procedure TBaseMediator.SetSubject(const AValue: TObject);

Var
  I : IFPObserved;

begin
  if FSubject=AValue then exit;
  If PublishedSubjectProperty then
    CheckPropertyName(AValue,FSubjectPropertyName);
  If FSubject<>Nil then
    If FSubject.GetInterface(SGUIDObserved,I) then
      I.FPODetachObserver(Self);
  If (AValue<>Nil) then
    begin
    If not AValue.GetInterface(SGUIDObserved,I) then
      Raise EObserver.CreateFmt(SErrObjectCannotBeObserved,[AValue.ClassName]);
    FSubject:=AValue;
    I.FPOAttachObserver(Self);
    end
  else
    FSubject:=AValue;
  MaybeObjectToView;
end;

procedure TBaseMediator.SetViewPropertyName(const AValue: String);
begin
  if FViewPropertyName=AValue then exit;
  If PublishedViewProperty then
    CheckPropertyName(GetView,AValue);
  FViewPropertyName:=AValue;
  MaybeObjectToView;
end;

class function TBaseMediator.PublishedViewProperty: Boolean;
begin
  Result:=True;
end;

class function TBaseMediator.PublishedSubjectProperty: Boolean;
begin
  Result:=True;
end;

procedure TBaseMediator.ViewChangedHandler(Sender: TObject);
begin
  MaybeViewToObject;
end;


procedure TBaseMediator.FPOObservedChanged(ASender: TObject;
  Operation: TFPObservedOperation; Data : Pointer);


begin
  If (ASender=FSubject) then
    begin
    If Operation=ooChange then
      MaybeObjectToView
    else if Operation=ooFree then
      FSubject:=Nil;
    end
  else if (ASender=FValueList) then
    begin
    If Operation=ooChange then
      ValueListChanged
    else if Operation=ooFree then
      FValueList:=Nil;
    end;
end;

procedure TBaseMediator.RaiseMediatorError(const Msg: String);
begin
  MediatorError(Self,Msg);
end;

procedure TBaseMediator.RaiseMediatorError(const Fmt: String;
  Args: array of const);
begin
  RaiseMediatorError(Format(FMT,Args));
end;

destructor TBaseMediator.Destroy;
begin
  Subject:=Nil;
  ValueList:=Nil;
  inherited Destroy;
end;

procedure TBaseMediator.DoObjectToView;

begin
  SetPropValue(GetView,ViewPropertyName,GetPropValue(FSubject,FSubjectPropertyName));
end;

procedure TBaseMediator.DoViewToObject;

begin
  SetPropValue(FSubject,FSubjectPropertyName,GetPropValue(GetView,ViewPropertyName));
end;

procedure TBaseMediator.ObjectToView;

Var
  B : Boolean;

begin
  If Not FTransferring then
    begin
    FTransferring:=True;
    try
      B:=False;
      If Assigned(FOnObjectToView) then
        FOnObjectToView(Self,B);
      If not B then
        DoObjectToView;
    finally
      FTransferring:=False;
    end;
    end;
end;

procedure TBaseMediator.ViewToObject;

Var
  B : Boolean;

begin
  If Not FTransferring then
    begin
    FTransferring:=True;
    try
      B:=False;
      If Assigned(FONViewToObject) then
        FONViewToObject(Self,B);
      If not B then
      DoViewToObject;
    finally
      FTransferring:=False;
    end;
    end;
end;

class function TBaseMediator.ViewClass: TClass;
begin
  Result:=TObject;
end;

class function TBaseMediator.CompositeMediator: Boolean;
begin
  Result:=False;
end;

{ TComponentMediator }

function TComponentMediator.GetView: TObject;
begin
  Result:=FViewComponent;
end;

procedure TComponentMediator.SetComponent(const AValue: TComponent);
begin
  If (Avalue=FViewComponent) then
    Exit;
  If Assigned(FViewComponent) then
    FViewComponent.RemoveFreeNotification(Self);
  FViewComponent:=AValue;
  If Assigned(FViewComponent) then
    FViewComponent.FreeNotification(Self);
  ViewChanged;
end;

procedure TComponentMediator.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);
  If (Operation=opRemove) and (AComponent=FViewComponent) then
    begin
    FViewComponent:=Nil;
    ViewChanged;
    end;
end;

{ TMediatorDef }

function TMediatorDef.Handles(ASubject: TObject; AGui: TComponent; APropInfo: PPropInfo): Boolean;
var
  N: string;
begin
  if (APropInfo = nil) then
    Result := FMC.CompositeMediator
  else
  begin
    N      := APropInfo^.Name;
    Result := True;
  end;
  if not Result then
    Exit; // ==>
  // At least the classes must match
  Result := AGui.InheritsFrom(FMC.ViewClass) and ASubject.InheritsFrom(FMSC);
  if Result and not FMC.CompositeMediator then
    if (PropertyName <> '') then
      Result := (CompareText(N, PropertyName) = 0)
    else // Property kind should match. Note that property MUST be set to something.
      Result := (APropInfo^.PropType^.Kind in PropertyTypes); // If PropertyName is set, it must match
end;

function TMediatorDef.BetterMatch(M: TMediatorDef): Boolean;
begin
  Result := (M = nil);
  if not Result then
  begin
    Result := (FMC.CompositeMediator = M.MediatorClass.CompositeMediator);
    if Result then
    begin
      Result := (PropertyName <> '') and (M.PropertyName = '');
      if not Result then
      begin
        // M's property matches closer
        Result := not ((M.PropertyName <> '') and (PropertyName = ''));
        if Result then
        begin
          // Properties are on equal level. Check GUI class.
          // Closer GUI class ?
          Result := FMC.ViewClass.InheritsFrom(M.MediatorClass.ViewClass);
          if not Result then
          begin
            // M's GUI class matches closer ?
            Result := not (M.MediatorClass.ViewClass.InheritsFrom(FMC.ViewClass));
            if Result then
            begin
              // GUI classes are on equal level (different branches in tree). Check subject class.
              // Closer Subject class ?
              Result := FMSC.InheritsFrom(M.FMSC);
              if not Result then
                // M's subject class matches closer ?
                Result := not M.FMSC.InheritsFrom(FMSC);
            end;
          end;
        end;
      end;
    end;
  end;
end;

{ TMediatorDefs }

function TMediatorDefs.GetDef(Index: integer): TMediatorDef;
begin
  Result := TMediatorDef(Items[Index]);
end;

procedure TMediatorDefs.SetDef(Index: integer; const AValue: TMediatorDef);
begin
  Items[Index] := AValue;
end;

function TMediatorDefs.AddDef: TMediatorDef;
begin
  Result := Add as TMediatorDef;
end;

{ TMediatorManager }

constructor TMediatorManager.Create;
begin
  FDefs := TMediatorDefs.Create(TMediatorDef);
end;

destructor TMediatorManager.Destroy;
begin
  FreeAndNil(FDefs);
  inherited Destroy;
end;

function TMediatorManager.FindDefFor(ASubject: TObject; AGui: TComponent): TMediatorDef;
begin
  Result := FindDefFor(ASubject, AGUI, PPropInfo(nil));
end;

function TMediatorManager.FindDefFor(ASubject: TObject; AGui: TComponent; const APropName: string): TMediatorDef;
var
  propinfo: PPropInfo;
begin
  propinfo := GetPropInfo(ASubject, APropName);
  Result := FindDefFor(ASubject, AGUI, propinfo);
end;

function TMediatorManager.FindDefFor(ASubject: TObject; AGui: TComponent; APropInfo: PPropInfo): TMediatorDef;
var
  D: TMediatorDef;
  I: integer;
begin
  Result := nil;
  for I := 0 to FDefs.Count - 1 do
  begin
    D := FDefs[I];
    if D.Handles(ASubject, AGUI, APropInfo) then
      if (D.BetterMatch(Result)) then
        Result := D;
  end;
end;

function TMediatorManager.RegisterMediator(MediatorClass: TMediatorClass; MinSubjectClass: TClass): TMediatorDef;

begin
  Result      := FDefs.AddDef;
  Result.MediatorClass := MediatorClass;
  Result.FMSC := MinSubjectClass;
  Result.FPN  := '';
  Result.FPT  := tkProperties - [tkClass, tkInterface, tkDynArray, tkObject, tkInterfaceRaw];
end;

function TMediatorManager.RegisterMediator(MediatorClass: TMediatorClass; MinSubjectClass: TClass; PropertyName: string): TMediatorDef;

begin
  Result      := FDefs.AddDef;
  Result.MediatorClass := MediatorClass;
  Result.FMSC := MinSubjectClass;
  Result.FPN  := PropertyName;
  Result.FPT  := [];
end;

function TMediatorManager.RegisterMediator(MediatorClass: TMediatorClass; MinSubjectClass: TClass; PropertyTypes: TTypeKinds): TMediatorDef;

begin
  Result      := FDefs.AddDef;
  Result.MediatorClass := MediatorClass;
  Result.FMSC := MinSubjectClass;
  Result.FPN  := '';
  Result.FPT  := PropertyTypes;
end;

{ TListItemMediator }


function TListItemMediator.GetActive: Boolean;
begin
  Result:=False;
  If Assigned(FListMediator) then
    Result:=FListMediator.Active;
end;

procedure TListItemMediator.SetSubject(const AValue: TObject);

Var
  I : IFPObserved;

begin
  if Avalue=FSubject then
    Exit;
  If FSubject<>Nil then
    If FSubject.GetInterface(SGUIDObserved,I) then
      I.FPODetachObserver(Self);
  FSubject:=AValue;
  If (FSubject<>Nil) then
    begin
    If not FSubject.GetInterface(SGUIDObserved,I) then
      Raise EObserver.CreateFmt(SErrObjectCannotBeObserved,[FSubject.ClassName]);
    I.FPOAttachObserver(Self);
    end;
  MaybeObjectToView
end;

procedure TListItemMediator.FPOObservedChanged(ASender: TObject;
  Operation: TFPObservedOperation; Data : Pointer);
begin
  If Operation=ooFree then
    FSubject:=Nil
  else
    MaybeObjectToView;
end;

procedure TListItemMediator.ObjectToView;
begin
  // Do nothing
end;

procedure TListItemMediator.ViewToObject;
begin
  // Do nothing
end;

destructor TListItemMediator.Destroy;
begin
  Subject:=Nil;
  inherited Destroy;
end;

procedure TListItemMediator.MaybeObjectToView;
begin
  If Assigned(FSubject) and Active then
    ObjectToView;
end;

{ TMediatorFieldInfo }

procedure TMediatorFieldInfo.Change;

begin
  FPONotifyObservers(Self,ooChange,Nil);
end;

function TMediatorFieldInfo.GetCaption: string;
begin
  Result:=FCaption;
  If (Result='') then
    Result:=FPropName;
end;

procedure TMediatorFieldInfo.SetAlign(const AValue: TAlignment);
begin
  If AValue=fAlign then Exit;
  FAlign:=AValue;
  Change;
end;

procedure TMediatorFieldInfo.SetCaption(const AValue: string);
begin
  If AValue=Caption then Exit;
  FCaption:=AValue;
  Change;
end;

procedure TMediatorFieldInfo.SetPropName(const AValue: string);
begin
  If AValue=FPropName then Exit;
  FPropName:=AValue;
  Change;
end;

procedure TMediatorFieldInfo.SetWidth(const AValue: Integer);
begin
  If (FWidth=AValue) then Exit;
  FWidth:=AValue;
  Change;
end;

const
  AlignChars: array[TAlignMent] of char     = ('l', 'r', 'c');

function TMediatorFieldInfo.GetAsString: string;
begin
  Result := Format('%s|%s|%d|%s', [PropertyName, AlignChars[Alignment], Width, Caption]);
end;

procedure TMediatorFieldInfo.SetAsString(const AValue: string);

  Function GetToken(Var S : String) : String;

  Var
    P : Integer;

  begin
    P:=Pos('|',S);
    If P=0 then P:=Length(S)+1;
    Result:=Copy(S,1,P-1);
    Delete(S,1,P);
  end;

var
  V,S: string;
  A: TAlignment;
  I: integer;

begin
  V:=S;
  I := 0;
  PropertyName:=GetToken(V);
  if (PropertyName = '') then
    MediatorError(Self,SErrInvalidFieldName, [Index + 1]);
  Alignment:=taLeftJustify;
  Width:=50;
  S:=GetToken(V);
  if (S<>'') then
    begin
    if (length(S)<>1) then
      MediatorError(Self,SErrInvalidAlignmentChar, [S,Index+1]);
    for A := Low(Talignment) to High(TAlignment) do
      if (Upcase(AlignChars[A])=Upcase(S[1])) then
        Alignment := A;
    S:=GetToken(V);
    if (S<>'') then
      begin
      if not TryStrToInt(S,i) then
        MediatorError(Self,SErrInvalidWidthSpecifier,[S]);
      Width:=I;
      S:=getToken(V);
      if (S<>'') then
        Caption := S;
      end;
    end;
end;

procedure TMediatorFieldInfo.Assign(Source: TPersistent);

Var
  M : TMediatorFieldInfo;

begin
  if (Source is TMediatorFieldInfo) then
    begin
    M:=Source as TMediatorFieldInfo;
    FWidth:=M.FWidth;
    FCaption:=M.FCaption;
    FPropName:=M.FPropname;
    FAlign:=M.FAlign;
    end
  else
    inherited Assign(Source);
end;

{ TColumnsListItemMediator }

function TColumnsListItemMediator.GetFieldsInfo: TMediatorFieldInfoList;
begin
  If Assigned(FListmediator) and (FListMediator is TColumnsListMediator) then
    Result:=TColumnsListMediator(FListMediator).FFieldsInfo;
end;

{ TBaseListMediator }

procedure TBaseListMediator.SetOnBeforeSetupField(
  const Value: TOnBeforeSetupField);

var
  I: integer;
begin
  FOnBeforeSetupField := Value;
  for I := 0 to FMediatorList.Count - 1 do
    TListItemMediator(FMediatorList[i]).OnBeforeSetupField := Value;
end;

class function TBaseListMediator.PublishedViewProperty: Boolean;
begin
  Result:=False;
end;

procedure TBaseListMediator.SetActive(const AValue: Boolean);

Var
  i : Integer;

begin
  inherited SetActive(AValue);
  If AValue then
    For I:=0 to MediatorList.Count-1 do
      TListItemMediator(MediatorList[i]).MaybeObjectToView;
end;

function TBaseListMediator.GetSelectedObject: TObject;
begin
  Result := nil;
end;

procedure TBaseListMediator.SetSelectedObject(const AValue: TObject);
begin
  // Do nothing
end;

procedure TBaseListMediator.DoDeleteItemMediator(AIndex: Integer;
  AMediator: TListItemMediator);
begin
  MediatorList.Delete(AIndex);
end;

Function TBaseListMediator.AddObject(AObject : TObject; AIndex : Integer) : TListItemMediator;


begin
  Result:=CreateItemMediator(AObject,AIndex);
  If (Result<>Nil) then
    begin
    Result.FListMediator:=Self;
    Result.Subject:=AObject;
    MediatorList.Add(Result);
    end;
end;

procedure TBaseListMediator.CreateSubMediators;

var
  I : integer;
  Model : TObjectList;
  
begin
  Model:=Subject as TObjectList;
  for i := 0 to Model.Count - 1 do
    begin
    if i < MediatorList.Count then
      TListItemMediator(MediatorList[i]).Subject := Model[i]
    else
      AddObject(Model[i], i);
    end;
  for i := MediatorList.Count-1 downto Model.Count do
    DoDeleteItemMediator(I,TListItemMediator(MediatorList[i]));
  FListChanged:=False;
end;

procedure TBaseListMediator.DoViewToObject;
begin
  // Do nothing
end;

procedure TBaseListMediator.DoObjectToView;
begin
  CreateSubMediators;
end;

procedure TBaseListMediator.SetSubject(const AValue: TObject);

Var
  V : TOBject;

begin
  if (AValue <> nil) then
    begin
    V:=Nil;
    if (AValue is TObjectList) then
      V:=AValue
    else If (AValue is TList) then
      V:=AValue
    else If (AValue is TCollection) then
      V:=AValue;
    if (V=Nil) then
      RaiseMediatorError(SErrNotListObject, [AValue.ClassName]);
    end;
  FListChanged:=True;
  inherited SetSubject(AValue)
end;

function TBaseListMediator.FindObjectMediator(AObject: TObject; out
  AtIndex: Integer): TListItemMediator;
begin
  AtIndex:=FMediatorList.Count-1;
  While (AtIndex>=0) and (TListItemMediator(FMediatorList[AtIndex]).Subject<>AObject) do
    Dec(AtIndex);
  If (AtIndex=-1) then
    Result:=Nil
  else
    Result:=TListItemMediator(FMediatorList[AtIndex]);
end;

constructor TBaseListMediator.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FMediatorList := TFPObjectList.Create;
  Active        := False;
  ViewPropertyName:='Caption';
end;

destructor TBaseListMediator.Destroy;
begin
  FreeAndNil(FMediatorList);
  inherited Destroy;
end;

class function TBaseListMediator.CompositeMediator: Boolean;
begin
  Result:=True;
end;

procedure TBaseListMediator.ObservedChanged(ASender: TObject;
  Operation: TFPObservedOperation);

var
  M     : TListItemMediator;
  Model : TObjectList;
  I     : Integer;

begin
  // Do not call inherited, it will rebuild the list !!
  Case Operation of
    ooAddItem    : AddObject(ASender,TObjectList(Subject).Count-1); // always at the end...
    ooDeleteItem : begin
                   M:=FindObjectMediator(ASender,I);
                   if M<>nil then
                     DoDeleteItemMediator(I,M);
                   end;
    ooChange    : begin
                   Model:=(Subject as TObjectList);
                   if FListChanged or (TObjectList(Model).Count<>MediatorList.Count) or (Model.Count=0) then // Safety measure
                     MaybeObjectToView;
                   end;
  end;

end;

{ TColumnsListMediator }

procedure TColumnsListMediator.SetFieldsInfo(
  const AValue: TMediatorFieldInfoList);
begin
  FFieldsInfo.Assign(AValue);
end;

function TColumnsListMediator.GetDisplayNames: string;
begin
  Result := FFieldsInfo.AsString;
end;

procedure TColumnsListMediator.SetDisplayNames(const AValue: string);
begin
  SubjectPropertyName:=AValue;
  ParseDisplayNames(AValue);
end;

procedure TColumnsListMediator.FieldInfoChanged(Item: TMediatorFieldInfo;
  Action: TCollectionNotification);
begin
  If Active  then
    RaiseMediatorError(SErrActive);
end;

class function TColumnsListMediator.PublishedSubjectProperty: Boolean;
begin
  Result:=False;
end;

procedure TColumnsListMediator.ParseDisplayNames(const AValue: string);

  Function GetToken(Var S : String) : String;

  Var
    P : Integer;

  begin
    P:=Pos(';',S);
    If P=0 then P:=Length(S)+1;
    Result:=Copy(S,1,P-1);
    Delete(S,1,P);
  end;

var
  I : integer;
  lField : string;
  MFI : TMediatorFieldInfo;
  A,S : String;

begin
  FFieldsInfo.Clear;
  A:=AValue;
  Repeat
    S:=GetToken(A);
    If (S<>'') then
      begin
      MFI:=FFieldsInfo.AddFieldInfo;
      MFI.AsString:=S;
      end;
  until (S='');
end;

procedure TColumnsListMediator.CreateColumns;
begin
  // Do nothing. Must be implemented by descendent objects.
end;

procedure TColumnsListMediator.DoObjectToView;
begin
  CreateColumns;
  inherited DoObjectToView;
end;

constructor TColumnsListMediator.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FFieldsInfo:=TMediatorFieldInfoList.create(TMediatorFieldInfo);
  SubjectPropertyName:='Caption';
end;

destructor TColumnsListMediator.Destroy;
begin
  FreeAndNil(FFieldsInfo);
  inherited Destroy;
end;

{ TMediatorFieldInfoList }

function TMediatorFieldInfoList.GetAsString: string;

var
  I: integer;
begin
  Result := '';
  for I := 0 to Count - 1 do
  begin
    if (Result <> '') then
      Result := Result + ';';
    Result := Result + FieldInfo[i].AsString;
  end;
end;

function TMediatorFieldInfoList.GetI(Index: integer): TMediatorFieldInfo;
begin
  Result := TMediatorFieldInfo(Items[Index]);
end;

procedure TMediatorFieldInfoList.SetI(Index: integer;
  const AValue: TMediatorFieldInfo);

begin
  Items[Index] := AValue;
end;

procedure TMediatorFieldInfoList.Notify(Item: TCollectionItem;
  Action: TCollectionNotification);
begin
  inherited Notify(Item, Action);
  If Assigned(FMediator) then
    FMediator.FieldInfoChanged(Item as TMediatorFieldInfo,Action)
end;

function TMediatorFieldInfoList.AddFieldInfo: TMediatorFieldInfo;
begin
  Result := Add as TMediatorFieldInfo;
end;

function TMediatorFieldInfoList.AddFieldInfo(const APropName: String;
  AFieldWidth: Integer): TMediatorFieldInfo;
begin
  Result:=AddFieldInfo();
  Result.PropertyName:=APropName;
  Result.Width:=AFieldWidth;
end;

function TMediatorFieldInfoList.AddFieldInfo(const APropName, ACaption: String;
  AFieldWidth: Integer): TMediatorFieldInfo;
begin
  Result:=AddFieldInfo(APropName,AFieldWidth);
  Result.Caption:=ACaption;
end;

function TMediatorFieldInfoList.AddFieldInfo(const APropName, ACaption: String;
  AFieldWidth: Integer; AAlignment: TAlignment): TMediatorFieldInfo;
begin
  Result:=AddFieldInfo(APropName,ACaption,AFieldWidth);
  Result.Alignment:=AAlignment;
end;

end.

