unit ORClasses;

interface

uses
  SysUtils, System.Types, Classes, Controls, ComCtrls, ExtCtrls, StdCtrls, Forms, ORFn;

type
  TNotifyProc = procedure(Sender: TObject);

  TORNotifyList = class(TObject)
  private
    FCode: TList;
    FData: TList;
  protected
    function GetItems(index: integer): TNotifyEvent;
    procedure SetItems(index: integer; const Value: TNotifyEvent);
    function GetIsProc(index: integer): boolean;
    function GetProcs(index: integer): TNotifyProc;
    procedure SetProcs(index: integer; const Value: TNotifyProc);
  public
    constructor Create;
    destructor Destroy; override;
    function IndexOf(const NotifyProc: TNotifyEvent): integer; overload;
    function IndexOf(const NotifyProc: TNotifyProc): integer; overload;
    procedure Add(const NotifyProc: TNotifyEvent); overload;
    procedure Add(const NotifyProc: TNotifyProc); overload;
    procedure Clear;
    function Count: integer;
    procedure Delete(index: integer);
    procedure Remove(const NotifyProc: TNotifyEvent); overload;
    procedure Remove(const NotifyProc: TNotifyProc); overload;
    procedure Notify(Sender: TObject);
    property Items[index: integer]: TNotifyEvent read GetItems write SetItems; default;
    property Procs[index: integer]: TNotifyProc read GetProcs write SetProcs;
    property IsProc[index: integer]: boolean read GetIsProc;
  end;

  TCanNotifyEvent = procedure(Sender: TObject; var CanNotify: boolean) of object;

  IORNotifier = interface(IUnknown)
    function GetOnNotify: TCanNotifyEvent;
    procedure SetOnNotify(Value: TCanNotifyEvent);
    procedure BeginUpdate;
    procedure EndUpdate(DoNotify: boolean = FALSE);
    procedure NotifyWhenChanged(Event: TNotifyEvent); overload;
    procedure NotifyWhenChanged(Event: TNotifyProc); overload;
    procedure RemoveNotify(Event: TNotifyEvent); overload;
    procedure RemoveNotify(Event: TNotifyProc); overload;
    procedure Notify; overload;
    procedure Notify(Sender: TObject); overload;
    function NotifyMethod: TNotifyEvent;
    property OnNotify: TCanNotifyEvent read GetOnNotify Write SetOnNotify;
  end;

  TORNotifier = class(TInterfacedObject, IORNotifier)
  private
    FNotifyList: TORNotifyList;
    FUpdateCount: integer;
    FOwner: TObject;
    FOnNotify: TCanNotifyEvent;
  protected
    procedure DoNotify(Sender: TObject);
  public
    constructor Create(Owner: TObject = nil; SingleInstance: boolean = FALSE);
    destructor Destroy; override;
    function GetOnNotify: TCanNotifyEvent;
    procedure SetOnNotify(Value: TCanNotifyEvent);
    procedure BeginUpdate;
    procedure EndUpdate(DoNotify: boolean = FALSE);
    procedure NotifyWhenChanged(Event: TNotifyEvent); overload;
    procedure NotifyWhenChanged(Event: TNotifyProc); overload;
    procedure RemoveNotify(Event: TNotifyEvent); overload;
    procedure RemoveNotify(Event: TNotifyProc); overload;
    procedure Notify; overload;
    procedure Notify(Sender: TObject); overload;
    function NotifyMethod: TNotifyEvent;
    property OnNotify: TCanNotifyEvent read GetOnNotify Write SetOnNotify;
  end;

  TORStringList = class(TStringList, IORNotifier)
  private
    FNotifier: IORNotifier;
  protected
    function GetNotifier: IORNotifier;
    procedure Changed; override;
  public
    destructor Destroy; override;
    procedure KillObjects;
// IndexOfPiece starts looking at StartIdx+1
    function CaseInsensitiveIndexOfPiece(Value: string; Delim: Char = '^';
                                         PieceNum: integer = 1;
                                         StartIdx: integer = -1): integer;
    function IndexOfPiece(Value: string; Delim: Char = '^';
                                         PieceNum: integer = 1;
                                         StartIdx: integer = -1): integer;
    function IndexOfPieces(const Values: array of string; const Delim: Char;
                                                  const Pieces: array of integer;
                                                  StartIdx: integer = -1): integer; overload;
    function IndexOfPieces(const Values: array of string): integer; overload;
    function IndexOfPieces(const Values: array of string; StartIdx: integer): integer; overload;
    function PiecesEqual(const Index: integer;
                         const Values: array of string): boolean; overload;
    function PiecesEqual(const Index: integer;
                         const Values: array of string;
                         const Pieces: array of integer): boolean; overload;
    function PiecesEqual(const Index: integer;
                         const Values: array of string;
                         const Pieces: array of integer;
                         const Delim: Char): boolean; overload;
    procedure SetStrPiece(Index, PieceNum: integer; Delim: Char; const NewValue: string); overload;
    procedure SetStrPiece(Index, PieceNum: integer; const NewValue: string); overload; 
    procedure SortByPiece(PieceNum: integer; Delim: Char = '^');
    procedure SortByPieces(Pieces: array of integer; Delim: Char = '^');
    procedure RemoveDuplicates(CaseSensitive: boolean = TRUE);
    property Notifier: IORNotifier read GetNotifier implements IORNotifier;
  end;

{ Do NOT add ANTHING to the ORExposed Classes except to change the scope
  of a property.  If you do, existing code could generate Access Violations }
  TORExposedCustomEdit = class(TCustomEdit)
  public
    property ReadOnly;
  end;

  TORExposedAnimate = class(TAnimate)
  public
    property OnMouseUp;
    property OnMouseDown;
  end;

  TORExposedControl = class(TControl)
  public
    property Font;
    property Text;
  end;

{ AddToNotifyWhenCreated allows you to add an event handler before the object that
  calls that event handler is created.  This only works when there is only one
  instance of a given object created (like TPatient or TEncounter).  For an object
  to make use of this feature, it must call ObjectCreated in the constructor,
  which will return the TORNotifyList that was created for that object. }
procedure AddToNotifyWhenCreated(ProcToAdd: TNotifyEvent; CreatedClass: TClass); overload;
procedure AddToNotifyWhenCreated(ProcToAdd: TNotifyProc; CreatedClass: TClass); overload;
procedure ObjectCreated(CreatedClass: TClass; var NotifyList: TORNotifyList);

type
  TORInterfaceList = class(TList)
  private
    function GetItem(Index: Integer): IUnknown;
    procedure SetItem(Index: Integer; const Value: IUnknown);
  protected
    procedure Notify(Ptr: Pointer; Action: TListNotification); override;
  public
    function Add(Item: IUnknown): Integer;
    function Extract(Item: IUnknown): IUnknown;
    function First: IUnknown;
    function IndexOf(Item: IUnknown): Integer;
    procedure Insert(Index: Integer; Item: IUnknown);
    function Last: IUnknown;
    function Remove(Item: IUnknown): Integer;
    property Items[Index: Integer]: IUnknown read GetItem write SetItem; default;
  end;

implementation

var
  NotifyLists: TStringList = nil;

function IndexOfClass(CreatedClass: TClass): integer;
begin
  if(not assigned(NotifyLists)) then
    NotifyLists := TStringList.Create;
  Result := NotifyLists.IndexOf(CreatedClass.ClassName);
  if(Result < 0) then
    Result := NotifyLists.AddObject(CreatedClass.ClassName, TORNotifyList.Create);
end;

procedure AddToNotifyWhenCreated(ProcToAdd: TNotifyEvent; CreatedClass: TClass); overload;
var
  idx: integer;

begin
  idx := IndexOfClass(CreatedClass);
  TORNotifyList(NotifyLists.Objects[idx]).Add(ProcToAdd);
end;

procedure AddToNotifyWhenCreated(ProcToAdd: TNotifyProc; CreatedClass: TClass); overload;
var
  idx: integer;

begin
  idx := IndexOfClass(CreatedClass);
  TORNotifyList(NotifyLists.Objects[idx]).Add(ProcToAdd);
end;

procedure ObjectCreated(CreatedClass: TClass; var NotifyList: TORNotifyList);
var
  idx: integer;

begin
  if(assigned(NotifyLists)) then
  begin
    idx := IndexOfClass(CreatedClass);
    if(idx < 0) then
      NotifyList := nil
    else
    begin
      NotifyList := (NotifyLists.Objects[idx] as TORNotifyList);
      NotifyLists.Delete(idx);
      if(NotifyLists.Count <= 0) then
        KillObj(@NotifyLists);
    end;
  end;
end;

{ TORNotifyList }

constructor TORNotifyList.Create;
begin
  inherited;
  FCode := TList.Create;
  FData := TList.Create;
end;

destructor TORNotifyList.Destroy;
begin
  KillObj(@FCode);
  KillObj(@FData);
  inherited
end;

function TORNotifyList.IndexOf(const NotifyProc: TNotifyEvent): integer;
var
  m: TMethod;

begin
  if(assigned(NotifyProc) and (FCode.Count > 0)) then
  begin
    m := TMethod(NotifyProc);
    Result := 0;
    while((Result < FCode.Count) and ((FCode[Result] <> m.Code) or
          (FData[Result] <> m.Data))) do inc(Result);
    if Result >= FCode.Count then Result := -1;
  end
  else
    Result := -1;
end;

procedure TORNotifyList.Add(const NotifyProc: TNotifyEvent);
var
  m: TMethod;

begin
  if(assigned(NotifyProc) and (IndexOf(NotifyProc) < 0)) then
  begin
    m := TMethod(NotifyProc);
    FCode.Add(m.Code);
    FData.Add(m.Data);
  end;
end;

procedure TORNotifyList.Remove(const NotifyProc: TNotifyEvent);
var
  idx: integer;

begin
  idx := IndexOf(NotifyProc);
  if(idx >= 0) then
  begin
    FCode.Delete(idx);
    FData.Delete(idx);
  end;
end;

function TORNotifyList.GetItems(index: integer): TNotifyEvent;
begin
  TMethod(Result).Code := FCode[index];
  TMethod(Result).Data := FData[index];
end;

procedure TORNotifyList.SetItems(index: integer; const Value: TNotifyEvent);
begin
  FCode[index] := TMethod(Value).Code;
  FData[index] := TMethod(Value).Data;
end;

procedure TORNotifyList.Notify(Sender: TObject);
var
  i: integer;
  evnt: TNotifyEvent;
  proc: TNotifyProc;

begin
  for i := 0 to FCode.Count-1 do
  begin
    if(FData[i] = nil) then
    begin
      proc := FCode[i];
      if(assigned(proc)) then proc(Sender);
    end
    else
    begin
      TMethod(evnt).Code := FCode[i];
      TMethod(evnt).Data := FData[i];
      if(assigned(evnt)) then evnt(Sender);
    end;
  end;
end;

procedure TORNotifyList.Clear;
begin
  FCode.Clear;
  FData.Clear;
end;

function TORNotifyList.Count: integer;
begin
  Result := FCode.Count;
end;

procedure TORNotifyList.Delete(index: integer);
begin
  FCode.Delete(index);
  FData.Delete(index);
end;

procedure TORNotifyList.Add(const NotifyProc: TNotifyProc);
begin
  if(assigned(NotifyProc) and (IndexOf(NotifyProc) < 0)) then
  begin
    FCode.Add(@NotifyProc);
    FData.Add(nil);
  end;
end;

function TORNotifyList.IndexOf(const NotifyProc: TNotifyProc): integer;
var
  prt: ^TNotifyProc;

begin
  prt := @NotifyProc;
  if(assigned(NotifyProc) and (FCode.Count > 0)) then
  begin
    Result := 0;
    while((Result < FCode.Count) and ((FCode[Result] <> prt) or
          (FData[Result] <> nil))) do inc(Result);
    if Result >= FCode.Count then Result := -1;
  end
  else
    Result := -1;
end;

procedure TORNotifyList.Remove(const NotifyProc: TNotifyProc);
var
  idx: integer;

begin
  idx := IndexOf(NotifyProc);
  if(idx >= 0) then
  begin
    FCode.Delete(idx);
    FData.Delete(idx);
  end;
end;

function TORNotifyList.GetIsProc(index: integer): boolean;
begin
  Result := (not assigned(FData[index]));
end;

function TORNotifyList.GetProcs(index: integer): TNotifyProc;
begin
  Result := FCode[index];
end;

procedure TORNotifyList.SetProcs(index: integer; const Value: TNotifyProc);
begin
  FCode[index] := @Value;
  FData[index] := nil;
end;

{ TORNotifier }

constructor TORNotifier.Create(Owner: TObject = nil; SingleInstance: boolean = FALSE);
begin
  FOwner := Owner;
  if(assigned(Owner) and SingleInstance) then
    ObjectCreated(Owner.ClassType, FNotifyList);
end;

destructor TORNotifier.Destroy;
begin
  KillObj(@FNotifyList);
  inherited;
end;

procedure TORNotifier.BeginUpdate;
begin
  inc(FUpdateCount);
end;

procedure TORNotifier.EndUpdate(DoNotify: boolean = FALSE);
begin
  if(FUpdateCount > 0) then
  begin
    dec(FUpdateCount);
    if(DoNotify and (FUpdateCount = 0)) then Notify(FOwner);
  end;
end;

procedure TORNotifier.Notify(Sender: TObject);
begin
  if((FUpdateCount = 0) and assigned(FNotifyList) and (FNotifyList.Count > 0)) then
    DoNotify(Sender);
end;

procedure TORNotifier.Notify;
begin
  if((FUpdateCount = 0) and assigned(FNotifyList) and (FNotifyList.Count > 0)) then
    DoNotify(FOwner);
end;

procedure TORNotifier.NotifyWhenChanged(Event: TNotifyEvent);
begin
  if(not assigned(FNotifyList)) then
    FNotifyList := TORNotifyList.Create;
  FNotifyList.Add(Event);
end;

procedure TORNotifier.NotifyWhenChanged(Event: TNotifyProc);
begin
  if(not assigned(FNotifyList)) then
    FNotifyList := TORNotifyList.Create;
  FNotifyList.Add(Event);
end;

procedure TORNotifier.RemoveNotify(Event: TNotifyEvent);
begin
  if(assigned(FNotifyList)) then
    FNotifyList.Remove(Event);
end;

procedure TORNotifier.RemoveNotify(Event: TNotifyProc);
begin
  if(assigned(FNotifyList)) then
    FNotifyList.Remove(Event);
end;

function TORNotifier.NotifyMethod: TNotifyEvent;
begin
  Result := Notify;
end;

function TORNotifier.GetOnNotify: TCanNotifyEvent;
begin
  Result := FOnNotify;
end;

procedure TORNotifier.SetOnNotify(Value: TCanNotifyEvent);
begin
  FOnNotify := Value;
end;

procedure TORNotifier.DoNotify(Sender: TObject);
var
  CanNotify: boolean;

begin
  CanNotify := TRUE;
  if(assigned(FOnNotify)) then
    FOnNotify(Sender, CanNotify);
  if(CanNotify) then
    FNotifyList.Notify(Sender);
end;

{ TORStringList }

destructor TORStringList.Destroy;
begin
  FNotifier := nil; // Frees instance
  inherited;
end;

procedure TORStringList.Changed;
var
  OldEvnt: TNotifyEvent;

begin
{ We redirect the OnChange event handler, rather than calling
  FNotifyList.Notify directly, because inherited may not call
  OnChange, and we don't have access to the private variables
  inherited uses to determine if OnChange should be called    }

  if(assigned(FNotifier)) then
  begin
    OldEvnt := OnChange;
    try
      OnChange := FNotifier.NotifyMethod;
      inherited; // Conditionally Calls FNotifier.Notify
    finally
      OnChange := OldEvnt;
    end;
  end;
  inherited; // Conditionally Calls the old OnChange event handler
end;

function TORStringList.IndexOfPiece(Value: string; Delim: Char;
                                                   PieceNum: integer;
                                                   StartIdx: integer): integer;
begin
  Result := StartIdx;
  inc(Result);
  while((Result >= 0) and (Result < Count) and
        (Piece(Strings[Result], Delim, PieceNum) <> Value)) do
    inc(Result);
  if(Result < 0) or (Result >= Count) then Result := -1;
end;

function TORStringList.IndexOfPieces(const Values: array of string; const Delim: Char;
                                           const Pieces: array of integer;
                                           StartIdx: integer = -1): integer;
var
  Done: boolean;

begin
  Result := StartIdx;
  repeat
    inc(Result);
    if(Result >= 0) and (Result < Count) then
      Done := PiecesEqual(Result, Values, Pieces, Delim)
    else
      Done := TRUE;
  until(Done);
  if(Result < 0) or (Result >= Count) then Result := -1;
end;

function TORStringList.IndexOfPieces(const Values: array of string): integer;
begin
  Result := IndexOfPieces(Values, U, [], -1);
end;

function TORStringList.IndexOfPieces(const Values: array of string;
  StartIdx: integer): integer;
begin
  Result := IndexOfPieces(Values, U, [], StartIdx);
end;

function TORStringList.GetNotifier: IORNotifier;
begin
  if(not assigned(FNotifier)) then
    FNotifier := TORNotifier.Create(Self);
  Result := FNotifier;
end;

procedure TORStringList.KillObjects;
var
  i: integer;

begin
  for i := 0 to Count-1 do
  begin
    if(assigned(Objects[i])) then
    begin
      Objects[i].Free;
      Objects[i] := nil;
    end;
  end;
end;

function TORStringList.PiecesEqual(const Index: integer;
                                   const Values: array of string): boolean;
begin
  Result := PiecesEqual(Index, Values, [], U);
end;

function TORStringList.PiecesEqual(const Index: integer;
                                   const Values: array of string;
                                   const Pieces: array of integer): boolean;
begin
  Result := PiecesEqual(Index, Values, Pieces, U);
end;

function TORStringList.PiecesEqual(const Index: integer;
                                   const Values: array of string;
                                   const Pieces: array of integer;
                                   const Delim: Char): boolean;
var
  i, cnt, p: integer;

begin
  cnt := 0;
  Result := TRUE;
  for i := low(Values) to high(Values) do
  begin
    inc(cnt);
    if(i >= low(Pieces)) and (i <= high(Pieces)) then
      p := Pieces[i]
    else
      p := cnt;
    if(Piece(Strings[Index], Delim, p) <> Values[i]) then
    begin
      Result := FALSE;
      break;
    end;
  end;
end;

procedure TORStringList.SortByPiece(PieceNum: integer; Delim: Char = '^');
begin
  SortByPieces([PieceNum], Delim);
end;

procedure TORStringList.RemoveDuplicates(CaseSensitive: boolean = TRUE);
var
  i: integer;
  Kill: boolean;

begin
  i := 1;
  while (i < Count) do
  begin
    if(CaseSensitive) then
      Kill := (Strings[i] = Strings[i-1])
    else
      Kill := (CompareText(Strings[i],Strings[i-1]) = 0);
    if(Kill) then
      Delete(i)
    else
      inc(i);
  end;
end;

function TORStringList.CaseInsensitiveIndexOfPiece(Value: string; Delim: Char = '^';
                            PieceNum: integer = 1; StartIdx: integer = -1): integer;
begin
  Result := StartIdx;
  inc(Result);
  while((Result >= 0) and (Result < Count) and
        (CompareText(Piece(Strings[Result], Delim, PieceNum), Value) <> 0)) do
    inc(Result);
  if(Result < 0) or (Result >= Count) then Result := -1;
end;

procedure TORStringList.SortByPieces(Pieces: array of integer;
  Delim: Char = '^');

  procedure QSort(L, R: Integer);
  var
    I, J: Integer;
    P: string;

  begin
    repeat
      I := L;
      J := R;
      P := Strings[(L + R) shr 1];
      repeat
        while ComparePieces(Strings[I], P, Pieces, Delim, TRUE) < 0 do Inc(I);
        while ComparePieces(Strings[J], P, Pieces, Delim, TRUE) > 0 do Dec(J);
        if I <= J then
        begin
          Exchange(I, J);
          Inc(I);
          Dec(J);
        end;
      until I > J;
      if L < J then QSort(L, J);
      L := I;
    until I >= R;
  end;

begin
  if not Sorted and (Count > 1) then
  begin
    Changing;
    QSort(0, Count - 1);
    Changed;
  end;
end;


procedure TORStringList.SetStrPiece(Index, PieceNum: integer; Delim: Char;
  const NewValue: string);
var
  tmp: string;

begin
  tmp := Strings[Index];
  ORFn.SetPiece(tmp,Delim,PieceNum,NewValue);
  Strings[Index] := tmp;
end;

procedure TORStringList.SetStrPiece(Index, PieceNum: integer;
  const NewValue: string);
begin
  SetStrPiece(Index, PieceNum, '^', NewValue);
end;

{ TORInterfaceList }

function TORInterfaceList.Add(Item: IUnknown): Integer;
begin
  Result := inherited Add(Pointer(Item));
end;

function TORInterfaceList.Extract(Item: IUnknown): IUnknown;
begin
  Result := IUnknown(inherited Extract(Pointer(Item)));
end;

function TORInterfaceList.First: IUnknown;
begin
  Result := IUnknown(inherited First);
end;

function TORInterfaceList.GetItem(Index: Integer): IUnknown;
begin
  Result := IUnknown(inherited Get(Index));
end;

function TORInterfaceList.IndexOf(Item: IUnknown): Integer;
begin
  Result := inherited IndexOf(Pointer(Item));
end;

procedure TORInterfaceList.Insert(Index: Integer; Item: IUnknown);
begin
  inherited Insert(Index, Pointer(Item));
end;

function TORInterfaceList.Last: IUnknown;
begin
  Result := IUnknown(inherited Last);
end;

procedure TORInterfaceList.Notify(Ptr: Pointer; Action: TListNotification);
begin
  case Action of
    lnAdded:                IUnknown(Ptr)._AddRef;
    lnDeleted, lnExtracted: IUnknown(Ptr)._Release;
  end;
end;

function TORInterfaceList.Remove(Item: IUnknown): Integer;
begin
  Result := inherited Remove(Pointer(Item));
end;

procedure TORInterfaceList.SetItem(Index: Integer; const Value: IUnknown);
begin
  inherited Put(Index, Pointer(Value));
end;


initialization

finalization
  KillObj(@NotifyLists, TRUE);

end.