unit IBOPipelineUnit;

//------------------------------------------------------------------------------
//
//  Corey Wangler
//  Designed Software Solutions
//  Adelaide, Australia.
//
//  mailto:cwangler@terra.net.au
//
//  Please feel free to contact me with any suggestions/fixes/problems.
//
//  DISCLAIMER:
//  This code is distributed AS-IS. No damages as a direct or indirect result
//  of using this product can be claimed or litigated against the provider.
//  In otherwords, use only at your own risk.
//
//------------------------------------------------------------------------------
// Nov-22-2000 : Telesis Computing <gworboys@zeta.org.au>
//
//      - Updated to compile under RB v5.5 (using C++Builder4 anyway).
//        No backward compatility installed so dont try to use this with
//        previous versions of RB.
//
//
//  1999-08-24 Changed WorkingColumnDataType to return the scaled delphi
//               datatype Currency for compatible TIB_ColumnNumeric columns.
//               This prevents rounding errors in the display of these fields,
//               and in any calculations (e.g. summing) done with TppDBCalc
//               report components.
//             Only 1-4 decimal places are supported by the Currency datatype,
//               so only up to NUMERIC(15,4) will return column type dtCurrency.
//             NOTE: Columns with more than 4 decimal places will return column
//               type dtString by default... thus preventing calculations using
//               such columns.  If this is a problem, you can set the
//               conditional compiler directive
//               //TODO// ADD COMPILER CONDITIONAL -- ???
//
//  1999-08-16 Renamed package and units to use "IBOPipeline" prefix, and
//               removed some units from "uses" clauses.
//  1999-08-07 Revised SetWorkingColumn to remember the last FieldName.
//  1999-08-07 Modified to keep up with developments in RBPro 4.11
//  1999-06-08 Fixed native bookmark routines.  They were not calling the
//               parent class's AddBookmark() method.
//  1999-05-28 Made DataSource and DataSet handling more robust.
//  1999-05-28 Checked for compatability with RBPro 4.07
//  1999-05-27 Fixed bug in AddNativeBookmarks() thanks to John Tomaselli.
//  1999-04-27 Checked for compatability with RBPro 4.06
//  1999-03-06 Modified to suit ReportBuilder Pro 4.04
//
//------------------------------------------------------------------------------

//Remove "x" in "x$DEFINE" to allow use of old pipeline name...
{x$DEFINE OLDNAME_TIB_Pipeline}

interface

uses
  Classes, SysUtils, Graphics, Forms, Dialogs, ppTypes, ppDB,
  ppComm, IB_Components,
  {$ifdef VER150}
  Variants
  {$endif}
  {$ifdef VER140}
  Variants
  {$endif}
  ;

type

//------------------------------------------------------------------------------
//TppIBOPipeline -- ReportBuilder DataPipeline for InterBase Objects
//------------------------------------------------------------------------------
//
//  The TppIBOPipeline component can be assigned to the DataPipeline property
//  of the TppReport and TppDetailBand classes.  This component allows the
//  developer to assign a TIB_DataSource component via the DataSource
//  property.
//
//  Added properties...
//
//    DataSource: TIB_DataSource
//      Determines the native IBO datasource from which the
//      pipeline will obtain its data.
//
//    CanOpenDataSet: boolean
//      Determines if the pipeline is allowed to open the dataset associated
//      with the datasource that is assigned to the DataSource property.
//
//    CanCloseDataSet: boolean
//      Determines if the pipeline is allowed to close the dataset associated
//      with the datasource that is assigned to the DataSource property.
//
//    HideRepeatedFields: boolean
//      Determines if repeated field names are hidden by
//      the pipeline's GetFieldNames method.
//
//      When set to true, all field names with the same fieldname as
//      another in the list will be hidden.
//
//      When set to false, all repeated fields will be shown in the list,
//      with a relation prefix added...  i.e. "<relname>.<fieldname>"
//
//    MaxDataSetBufferRowCount: integer
//      If the dataset is buffered, this property can be used to limit the
//      number of records that will be buffered by the dataset.
//
//      When the number of records stored in the dataset's buffer
//      reaches this value, the buffer will be cleared.
//
//      NOTE: MaxDataSetBufferRowCount=0 turns this feature off.
//
//  Added Events...
//
//    property OnBeforeDelete: TNotifyEvent
//    property OnAfterDelete: TNotifyEvent
//    property OnBeforeEdit: TNotifyEvent
//    property OnAfterEdit: TNotifyEvent
//    property OnBeforeInsert: TNotifyEvent
//    property OnAfterInsert: TNotifyEvent
//    property OnBeforePost: TNotifyEvent
//    property OnAfterPost: TNotifyEvent
//
//  Added read-only (runtime) properties...
//
//    property DataSet: TIB_DataSet
//    property DataSetIsBuffered: boolean
//
//  Added methods...
//
//    procedure ClearBookmarks;
//      Clears the pipeline's list of bookmarks.
//      NOTE: do not use "ClearBookmarkList", as it would leave entries in the
//      bookmark mapping list... which could then get large after repeated use.
//
//    function AddNativeBookmark(ANativeBookmark: string): longint;
//      Add a native bookmark to the pipeline's list of bookmarks.
//
//    procedure AddNativeBookmarks(ANativeBookmarkList: TStrings);
//      Add a bunch of native bookmarks to the pipeline's list of bookmarks.
//
//    procedure SetBookmarks(ANativeBookmarkList: TStrings);
//      Set *all* of the pipeline's list of bookmarks
//      via a list of native (IBO) bookmarks.
//
//    //TODO// -- IMPLEMENT THE FOLLOWING METHOD...
//    procedure CopySelectedBookmarks(
//                ANativeQuery: TIB_BDataset;
//                UseCurrentIfNoMultiSelect: boolean);
//      Set *all* of the pipeline's list of bookmarks to the records
//      multi-selected in a native IBO query.  Optionally use the currently
//      selected record if there are no multi-selected records -- this is
//      handy for restricting the the reporting to just the selected record
//      when records have not been multi-selected.
//
//------------------------------------------------------------------------------

//------------------------------------------------------------------------------
//DEVELOPMENT NOTES
//------------------------------------------------------------------------------
//
//  When checking for compatability with newer version of ReportBuilder, do
//  a file compare on ppDBPipe.pas, as the TppIBOPipeline should implement
//  basically the same code as TppDBPipeline with appropriate modifications
//  to work with IB Objects.
//
//------------------------------------------------------------------------------

TppIBOPipeline = class(TppCustomDataPipeline)

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Basically the same stuff as in TppDBPipeline's private section, but
  // modified for use with IB Objects components.
  //
  private
    FDataLink: TIB_DataLink;  //<=-mainly to keep track of events
    FDataSource: TIB_DataSource;
    FDataSet: TIB_DataSet;
    FWorkingColumn: TIB_Column;
    FWorkingColumnAccessFieldName: string;
    FRecursionFlag: integer;
    FCanOpenDataSet: boolean;
    FCanCloseDataSet: boolean;
    FRefreshAfterPost: boolean;

    FIBOBookmarkList: TStringList;  //<=-map bookmarks (IBO string <-> longint)
    FIBOBookmarkCount: integer;
    //FCacheBookmarks: TList;  //<=-keep track of bookmarks used by cached pages

    {DataLink event handlers...}
    procedure DataLinkActiveChanged(
      Sender: TIB_DataLink; ADataSource: TIB_DataSource);
    procedure DataLinkStateChanged(
      Sender: TIB_DataLink; ADataSource: TIB_DataSource);
    {Bookmark routines...}
    //procedure FreeAllPageBookmarks;
    //procedure FreePageBookmarks(AStartPageNo: longint);
    {DataSource / DataSet / Working column...}
    procedure SetDataSource(ADataSource: TIB_DataSource);
    function SetWorkingColumn(AFieldName: string): boolean;
    function WorkingColumnDataType: TppDataType;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // ADDED PRIVATE FIELDS AND METHODS
  //
  private
    FHideRepeatedFields: boolean;
    FDataSetIsBuffered: boolean;
    FMaxDataSetBufferRowCount: integer;

    FOnBeforeDelete: TNotifyEvent;
    FOnAfterDelete: TNotifyEvent;
    FOnBeforeEdit: TNotifyEvent;
    FOnAfterEdit: TNotifyEvent;
    FOnBeforeInsert: TNotifyEvent;
    FOnAfterInsert: TNotifyEvent;
    FOnBeforePost: TNotifyEvent;
    FOnAfterPost: TNotifyEvent;

    FTemporaryPicture: TPicture;  //<=-for GetFieldAsPicture method

    procedure SetInternalDataSet;
    procedure CheckDataSetPrepared;

    {
    Limit the DataSet's buffer use...
    NOTE: MaxDataSetBufferRowCount=0 turns this feature off.
    }
    procedure SetMaxDataSetBufferRowCount(AMaxBufferRowCount: integer);
    procedure LimitDataSetBufferUsage;

    function NonBufferedGotoNativeBookmark(
      ADataSet: TIB_DataSet;
      ANativeBookmark: string): boolean;
    function NonBufferedLocate(
      ADataSet: TIB_DataSet;
      const AKeyFields: string;
      const AKeyValues: Variant;
      AOptions: TIB_LocateOptions): boolean;

    function ConvertNativeBookmark(ANativeBookmark: string): longint;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // ADDED PUBLISHED PROPERTIES
  //
  published
    {New properties...}
    property DataSource: TIB_DataSource read FDataSource
                                        write SetDataSource;
    property CanOpenDataSet: boolean read FCanOpenDataSet
                                     write FCanOpenDataSet
                                     default true;
    property CanCloseDataSet: boolean read FCanCloseDataSet
                                      write FCanCloseDataSet
                                      default false;
    property HideRepeatedFields: boolean read FHideRepeatedFields
                                         write FHideRepeatedFields
                                         default true;
    property MaxDataSetBufferRowCount: integer read FMaxDataSetBufferRowCount
                                               write SetMaxDataSetBufferRowCount
                                               default 0;

    {New event properties...}
    property OnBeforeDelete: TNotifyEvent read FOnBeforeDelete
                                          write FOnBeforeDelete;
    property OnAfterDelete: TNotifyEvent read FOnAfterDelete
                                         write FOnAfterDelete;
    property OnBeforeEdit: TNotifyEvent read FOnBeforeEdit
                                        write FOnBeforeEdit;
    property OnAfterEdit: TNotifyEvent read FOnAfterEdit
                                       write FOnAfterEdit;
    property OnBeforeInsert: TNotifyEvent read FOnBeforeInsert
                                          write FOnBeforeInsert;
    property OnAfterInsert: TNotifyEvent read FOnAfterInsert
                                         write FOnAfterInsert;
    property OnBeforePost: TNotifyEvent read FOnBeforePost
                                        write FOnBeforePost;
    property OnAfterPost: TNotifyEvent read FOnAfterPost
                                       write FOnAfterPost;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // ADDED PUBLIC PROPERTIES
  //
  public
    {New (read-only) runtime properties...}
    property DataSet: TIB_DataSet read FDataSet;
    property DataSetIsBuffered: boolean read FDataSetIsBuffered;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // ADDED PUBLIC METHODS
  //
  public
    procedure Loaded; override; //<=-for setup of the pipeline once loaded

    {
    New methods for modifying the pipeline's list of bookmarks.
    If the pipeline has bookmarks defined, the list of bookmarks
    will be traversed when the report is printed.
    }
    procedure ClearBookmarks;
    function AddNativeBookmark(ANativeBookmark: string): longint;
    procedure AddNativeBookmarks(ANativeBookmarkList: TStrings);
    procedure SetBookmarks(ANativeBookmarkList: TStrings);
    //TODO//
    //procedure CopySelectedBookmarks(
    //  ANativeQuery: TIB_BDataset;
    //  UseCurrentIfNoMultiSelect: boolean);

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Exactly the same stuff as in TppDBPipeline's protected section
  // (re-ordered).
  //
  protected
    procedure OpenDataSet; override;
    procedure CloseDataSet; override;
    function GetActive: boolean; override;
    function CheckBOF: boolean; override;
    function CheckEOF: boolean; override;
    procedure GotoFirstRecord; override;
    procedure GotoLastRecord; override;
    //procedure TraverseBy(AIncrement: integer); override;
    function TraverseBy(aIncrement: Integer): Integer; override;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Exactly the same stuff as in TppDBPipeline's public section
  // (re-ordered).
  //
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Notify(
      ACommunicator: TppCommunicator;
      AOperation: TppOperationType); override;
    procedure Notification(
      AComponent: TComponent;
      Operation: TOperation); override;

    //procedure FreeCache(ACachePageNo: longint); override;
    //procedure RestoreFromCache(ACachePageNo: longint); override;
    //procedure SaveToCache(ACachePageNo: longint); override;

    function GetBookmark: longint; override;
    procedure FreeBookmark(ABookmark: longint); override;
    procedure GotoBookmark(ABookmark: longint); override;

    function GetDataSetName: string; override;
    function GetFieldNames(AFieldNameList: TStrings): boolean; override;
    function GetFieldDataType(AFieldName: string): TppDataType; override;
    function GetFieldAlignment(AFieldName: string): TAlignment; override;
    function GetFieldDisplayWidth(AFieldName: string): integer; override;
    function GetFieldIsCalculated(AFieldName: string): boolean; override;
    function GetFieldSize(AFieldName: string): integer; override;
    function GetFieldValue(AFieldName: string): Variant; override;
    function GetFieldAsString(AFieldName: string): string; override;
    function GetFieldAsDouble(AFieldName: string): Double; override;
    function GetFieldAsPicture(AFieldName: string): TPicture; override;
    procedure GetFieldAsStream(AFieldName: string; AStream: TStream); override;
    function  GetFieldIsNull(aFieldName: String): Boolean; override;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Exactly the same stuff as in TppDBPipeline's "optional" public section
  // (re-ordered).
  //
  // These search/edit/insert routines are required if you want to
  // load and save Report templates to database blob fields
  // as in the End-User Framework.
  //
  public
    procedure Delete; override;
    procedure Edit;  override;
    procedure Insert; override;
    procedure Post; override;

    function Locate(
      const AFieldName: string;
      AKeyValue: Variant;
      AOptions: TppLocateOptions): boolean; override;

    procedure SetFieldFromStream(AFieldName: string; AStream: TStream); override;
    procedure SetFieldValue(AFieldName: string; AValue: Variant); override;
    procedure CreateDefaultFields; override;

  //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  // Same stuff as in TppDBPipeline's published section
  //
  published
    {Publish TppDataPipeline properties...}
    property RefreshAfterPost: boolean read FRefreshAfterPost
                                       write FRefreshAfterPost
                                       default false;
    property AutoCreateFields;
    property MasterDataPipeline;
    property MoveBy;
    property RangeEnd;
    property RangeEndCount;
    property RangeBegin;
    property SkipWhenNoRecords;
    property UserName;
    property Visible;

    {Publish TppDataPipeline events...}
    property OnClose;
    property OnDataChange;
    property OnFirst;
    property OnGotoBookmark;
    property OnLast;
    property OnMasterRecordPositionChange;
    property OnNext;
    property OnOpen;
    property OnPrior;
    property OnRecordPositionChange;
    property OnTraversal;
  end;

//------------------------------------------------------------------------------
//TppChildIBOPipeline -- ReportBuilder Child DataPipeline for InterBase Objects
//------------------------------------------------------------------------------
//
//  Defined here for use with the ReportBuilder
//  Data Access Development Environment (DADE).
//
//------------------------------------------------------------------------------

TppChildIBOPipeline = class(TppIBOPipeline)
  public
    function HasParent: boolean; override;
  published
    property DataSource stored false;
  end;

//------------------------------------------------------------------------------
//TppChildIBODataSource -- ReportBuilder Child DataSource for InterBase Objects
//------------------------------------------------------------------------------
//
//  Defined here for use with the ReportBuilder
//  Data Access Development Environment (DADE).
//
//------------------------------------------------------------------------------

TppChildIBODataSource = class(TIB_DataSource)
  public
    function HasParent: boolean; override;
  end;

//==============================================================================
//==============================================================================

implementation

//------------------------------------------------------------------------------
//TppIBOPipeline -- Create/Destroy
//------------------------------------------------------------------------------

constructor TppIBOPipeline.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  FTemporaryPicture := nil;

  {Initialize added private fields first...}
  FHideRepeatedFields := true;
  FDataSetIsBuffered := false;
  FMaxDataSetBufferRowCount := 0;

  FDataSource := nil;
  FDataSet := nil;
  FWorkingColumn := nil;
  FWorkingColumnAccessFieldName := '';

  FCanOpenDataSet := true;  //<=-default property value
  FCanCloseDataSet := false;  //<=-default property value
  FRefreshAfterPost := false;

  FDataLink := TIB_DataLink.Create(self);
  FDataLink.OnActiveChange := DataLinkActiveChanged;
  FDataLink.OnStateChanged := DataLinkStateChanged;

  FIBOBookmarkList := TStringList.Create;
  FIBOBookmarkCount := 0;
  //FCacheBookmarks := TList.Create;
end;

destructor TppIBOPipeline.Destroy;
begin
  Destroying;
  Close;

  FDataLink.OnActiveChange := nil;
  FDataLink.OnStateChanged := nil;
  FDataLink.Free;
  FDataLink := nil;

  FIBOBookmarkList.Free;
  FIBOBookmarkList := nil;
  //FCacheBookmarks.Free;
  //FCacheBookmarks := nil;

  FTemporaryPicture.Free;
  FTemporaryPicture := nil;

  inherited Destroy;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- setup the pipeline after it is loaded
//------------------------------------------------------------------------------

procedure TppIBOPipeline.Loaded;
begin
  inherited Loaded;

  //TODO// -- REMOVE THIS LATER ??? (IT WAS NECESSARY FOR CONVERSION)
  {Generate a UserName for the pipeline if one is not assigned...}
  if Length(UserName) <= 0 then begin
    {check whether Name contains 'pp' or 'ra' etc, if so strip it off}
    if Copy(Name, 1,2) = Copy(ClassName, 2,2) then begin
      SetUserName( Copy(Name, 3, Length(Name)) );
    end else begin
      SetUserName( Name );
    end;
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Set the DataSource for the DataPipeline
//------------------------------------------------------------------------------

procedure TppIBOPipeline.SetDataSource(ADataSource: TIB_DataSource);
begin
  if (FDataSource <> ADataSource) then begin
    FDataLink.DataSource := nil;
    FDataSource := ADataSource;
    if Assigned(FDataSource) then begin
      FDataLink.DataSource := FDataSource;
      FDataSource.FreeNotification(self);
    end;
    SetInternalDataSet;
  end;
  SyncFields;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Set the internal FDataSet for the DataPipeline
//                    based on the current FDataSource's DataSet.
//------------------------------------------------------------------------------

procedure TppIBOPipeline.SetInternalDataSet;
begin
  if Assigned(FDataSource) then begin
    if (FDataSet <> FDataSource.DataSet) then begin
      FDataSet := FDataSource.DataSet;
      FDataSetIsBuffered := (FDataSet is TIB_BDataSet);
      if Assigned(FDataSet) then begin
        FDataSet.FreeNotification(self);
      end;
      {Make sure the current working column reference cannot be used...}
      FWorkingColumn := nil;
      FWorkingColumnAccessFieldName := '';
    end;
  end else begin
    FDataSet := nil;
    FDataSetIsBuffered := false;
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Make sure that the internal FDataSet is prepared.
//------------------------------------------------------------------------------

procedure TppIBOPipeline.CheckDataSetPrepared;
begin
  if (FDataSet <> nil) and (csDestroying in FDataSet.ComponentState) then Exit;

  if (FDataLink.DataSet <> nil) then begin
    with FDataLink.DataSet do begin
      if not Prepared and not (csDestroying in ComponentState) then begin
        Prepare;
      end;
    end;
  end;
  SetInternalDataSet;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Set the current working column
//------------------------------------------------------------------------------

function TppIBOPipeline.SetWorkingColumn(AFieldName: string): boolean;
begin
  AFieldName := UpperCase(Trim(AFieldName));

  {Use the current working column if it is already the correct one...}
  if Assigned(FWorkingColumn) and Assigned(FDataSet)
      and (FWorkingColumn.Statement = FDataSet)
      and (FWorkingColumnAccessFieldName = AFieldName) then begin
    Result := true;
    Exit;
  end;

  {Try to find the field in the dataset...}
  try
    if Assigned(FDataSet) then begin
      Result := FDataSet.Fields.GetByName(AFieldName, FWorkingColumn);
      FWorkingColumnAccessFieldName := AFieldName;
    end else begin
      Result := false;
    end;
  except
    //trap and ignore any exceptions
    Result := false;
  end;

  {Make sure the working column cannot be used if it was not set...}
  if (Result = false) then begin
    FWorkingColumn := nil;
    FWorkingColumnAccessFieldName := '';
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- DataLink event handlers
//------------------------------------------------------------------------------

procedure TppIBOPipeline.DataLinkActiveChanged(
  Sender: TIB_DataLink; ADataSource: TIB_DataSource);
begin
  {Make sure the current working column reference cannot be used...}
  FWorkingColumn := nil;
  FWorkingColumnAccessFieldName := '';

  {Call TppDataPipeline.Close or TppDataPipeline.DoOnDataChange...}
  if Assigned(FDataSource)
      and (csDestroying in FDataSource.ComponentState) then begin
    {FDataSource being destroyed, so call TppDataPipeline.Close...}
    Close;
  end else if Assigned(FDataSet)
               and (csDestroying in FDataSet.ComponentState) then begin
    {FDataSet being destroyed, so call TppDataPipeline.Close...}
    Close;
  end else if not (csDestroying in ComponentState)
               and Assigned(FDataSet)
               and not (FDataSet.Active) then begin
    {FDataSet being closed, so call TppDataPipeline.Close...}
    Close;
  end else begin
    {Call TppDataPipeline.DoOnDataChange...}
    DoOnDataChange;
  end;

  {Call TppDataPipeline.FreeFields if required...}
  if not (csDestroying in ComponentState) then begin
    if AutoCreateFields then begin
      if Active then begin
        SyncFields;
      end else begin
        FreeFields;
      end;
    end;
  end;
end;

{
NOTE: OnStateChanged gets fired when the DataSource's DataSet property is
modified.
}
procedure TppIBOPipeline.DataLinkStateChanged(
  Sender: TIB_DataLink; ADataSource: TIB_DataSource);
begin
  {Take appropriate action when DataSource's DataSet property is modified...}
  if Assigned(FDataSource) and (FDataSource.DataSet <> FDataSet) then begin
    try
      SetInternalDataSet;
    finally
      {Call TppDataPipeline.DoOnDataChange...}
      DoOnDataChange;
    end;
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Limit the DataSet's buffer use
//------------------------------------------------------------------------------

procedure TppIBOPipeline.SetMaxDataSetBufferRowCount(AMaxBufferRowCount: integer);
begin
  FMaxDataSetBufferRowCount := AMaxBufferRowCount;

  LimitDataSetBufferUsage;
end;

{
NOTE: FMaxDataSetBufferRowCount=0 turns this feature off
}
procedure TppIBOPipeline.LimitDataSetBufferUsage;
begin
  if (FMaxDataSetBufferRowCount > 0) and
      Assigned(FDataSet) and (FDataSetIsBuffered) and
      (FDataSet.BufferRowCount >= FMaxDataSetBufferRowCount) then begin
    FDataSet.InvalidateRows;
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- DataSet Open/Close/GetActive
//------------------------------------------------------------------------------

{
Open the dataset.
NOTE: SetInternalDataSet calls "FDataSet.FreeNotification(self)"
(as is done in TppDBPipeline.OpenDataSet).
}
procedure TppIBOPipeline.OpenDataSet;
begin
  if (FDataSet <> nil) and (csDestroying in FDataSet.ComponentState) then Exit;

  if FCanOpenDataSet and (FDataLink.DataSet <> nil) then begin
    FDataLink.DataSet.Open;
  end;
  SetInternalDataSet;
end;

procedure TppIBOPipeline.CloseDataSet;
begin
  if FCanCloseDataSet and (FDataLink.DataSet <> nil) then begin
    FDataLink.DataSet.Close;
  end;
end;

function TppIBOPipeline.GetActive: boolean;
begin
  if Assigned(FDataLink) then begin
    Result := FDataLink.Active;
  end else begin
    Result := false;
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- BOF/EOF checking
//------------------------------------------------------------------------------

function TppIBOPipeline.CheckBOF: boolean;
begin
  if Assigned(FDataSet) then begin
    Result := FDataSet.BOF;
  end else begin
    Result := true;
  end;
end;

function TppIBOPipeline.CheckEOF: boolean;
begin
  if Assigned(FDataSet) then begin
    Result := FDataSet.EOF;
  end else begin
    Result := true;
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Record navigation
//------------------------------------------------------------------------------

procedure TppIBOPipeline.GotoFirstRecord;
begin
  LimitDataSetBufferUsage;

  if Assigned(FDataSet) then begin
    FDataSet.First;
  end;
end;

procedure TppIBOPipeline.GotoLastRecord;
begin
  LimitDataSetBufferUsage;

  if Assigned(FDataSet) then begin
    FDataSet.Last;
  end;
end;

//procedure TppIBOPipeline.TraverseBy(AIncrement: integer);
function TppIBOPipeline.TraverseBy(aIncrement: Integer): Integer;
begin
  LimitDataSetBufferUsage;

  if Assigned(FDataSet) then begin
    Result := FDataSet.MoveBy(AIncrement);
  end else Result := 0;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Notification
//------------------------------------------------------------------------------

procedure TppIBOPipeline.Notify(
  ACommunicator: TppCommunicator;
  AOperation: TppOperationType);
begin
  inherited Notify(ACommunicator, AOperation);

  if (ACommunicator = self) and (AOperation = ppopDataChange) then begin
    CreateDefaultFields;
  end;
end;

procedure TppIBOPipeline.Notification(
  AComponent: TComponent;
  Operation: TOperation);
begin
  inherited Notification(AComponent, Operation);

  {Clean up after removal of components...}
  if (Operation = opRemove) then begin
    if (AComponent = FDataSource) then begin
      Close;
      SetDataSource(nil);
    end else if (AComponent = FDataSet) then begin
      Close;
      FDataSet := nil;
    end;
  end;
end;


//------------------------------------------------------------------------------
//TppIBOPipeline -- Added bookmark routines
//
//  These routines work with the list of bookmarks which will be traversed
//  when the report is printed.
//------------------------------------------------------------------------------

{
Added method to clear the pipeline's list of bookmarks.
}
procedure TppIBOPipeline.ClearBookmarks;
begin
  FreeBookmarks;
end;

{
Added method to add a native bookmark
to the pipeline's list of bookmarks.
}
function TppIBOPipeline.AddNativeBookmark(ANativeBookmark: string): longint;
begin
  {Default return 0 (no bookmark) if unable to store bookmark mapping info...}
  Result := ConvertNativeBookmark(ANativeBookmark);

  {Make the parent class store the bookmark (used for record traversal)...}
  if (Result <> 0) then begin
    AddBookmark(Result);
  end;
end;

{
Added method to add a bunch of native bookmarks
to the pipeline's list of bookmarks.
}
procedure TppIBOPipeline.AddNativeBookmarks(ANativeBookmarkList: TStrings);
var
  ii: integer;
begin
  if not Assigned(ANativeBookmarkList) then Exit;
  for ii := 0 to ANativeBookmarkList.Count-1 do begin
    AddNativeBookmark( ANativeBookmarkList.Strings[ii] );
  end;
end;

{
Added method to set *all* of the pipeline's list of bookmarks via a
string list of native (IBO) bookmarks.
}
procedure TppIBOPipeline.SetBookmarks(ANativeBookmarkList: TStrings);
begin
  ClearBookmarks;
  AddNativeBookmarks(ANativeBookmarkList);
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Get/Free/Goto bookmark
//
//  These routines effectively map the IBO bookmarks (strings) to the
//  longint bookmarks used by ReportBuilder.
//
//  NOTE:  A IBO empty bookmark is an empty string... i.e. ''
//         A ReportBuilder empty bookmark is a longint zero
//------------------------------------------------------------------------------

function TppIBOPipeline.GetBookmark: longint;
begin
  {Exit and return 0 (no bookmark) if unable to get the bookmark...}
  Result := 0;
  if not Assigned(FDataSet) then Exit;
  Result := ConvertNativeBookmark( FDataSet.Bookmark );
end;

{
Utility method that converts a native IBO bookmark (string) into a form that
can be used by Report Builder (longint).  This routine stores mapping info
into FIBOBookmarkList for future reference of the bookmark.  Note that this
routine does NOT modify the parent class's list of bookmarks.
}
function TppIBOPipeline.ConvertNativeBookmark(ANativeBookmark: string): longint;
var
  TheStorageIndex: integer;
begin
  {Default return 0 (no bookmark) if unable to store the bookmark...}
  Result := 0;
  if (Length(ANativeBookmark) = 0) then Exit;

  with FIBOBookmarkList do begin
    {Search for an unused storage slot...}
    TheStorageIndex := -1;
    if (Count > FIBOBookmarkCount) then begin
      TheStorageIndex := IndexOf('');
    end;

    {Store the bookmark...}
    if (TheStorageIndex >= 0) then begin  //...store at unused slot
      Strings[TheStorageIndex] := ANativeBookmark;
      Result := TheStorageIndex + 1;
    end else begin  //...add to end of storage list
      Add( ANativeBookmark );
      Result := Count;  //<=-NOTE: one greater than the index
    end;

    {Keep track of the number of stored bookmarks...}
    Inc(FIBOBookmarkCount);
  end;
end;

procedure TppIBOPipeline.FreeBookmark(ABookmark: longint);
begin
  {Allow re-use of the storage slot...}
  with FIBOBookmarkList do begin
    if (ABookmark > 0) and (ABookmark <= Count) then begin
      Strings[ ABookmark-1 ] := '';  //<=-just set to a blank string
    end else begin
      Exit;  //<=-don't do anything!!!!!!! (ABookmark=0 or out of bounds)
    end;
  end;

  {Keep track of the number of stored bookmarks...}
  Dec(FIBOBookmarkCount);

  {Clear the storage list if no slots are in use...}
  if (FIBOBookmarkCount <= 0) then begin
    FIBOBookmarkCount := 0;
    FIBOBookmarkList.Clear;
  end;
end;

{
NOTE: This routine should be as efficient as possible
so that report generation stays as *fast* as possible.
}
procedure TppIBOPipeline.GotoBookmark(ABookmark: longint);
var
  TheIBOBookmark: string;
begin
  if Assigned(FDataSet) then begin
    {Clean up records in the dataset's buffer when required...}
    LimitDataSetBufferUsage;  //<=-for bookmarked traversal, etc

    {Goto the bookmark...}
    with FIBOBookmarkList do begin
      if (ABookmark > 0) and (ABookmark <= Count) then begin
        TheIBOBookmark := Strings[ ABookmark-1 ];
        if (Length(TheIBOBookmark) > 0) then begin
          if (FDataSetIsBuffered) then begin
            FDataSet.Bookmark := TheIBOBookmark;
          end else begin
            NonBufferedGotoNativeBookmark(FDataSet, TheIBOBookmark);
          end;
        end;
      end;
    end;

    {Call inherited to allow for bookmarklist traversal...}
    inherited GotoBookmark(ABookmark);
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline -- Get info from DataSet or Field
//------------------------------------------------------------------------------

function TppIBOPipeline.GetDataSetName: string;
begin
  if Assigned(FDataSet) then begin
    Result := FDataSet.Name;
  end else begin
    Result := '';
  end;
end;

function TppIBOPipeline.GetFieldNames(AFieldNameList: TStrings): boolean;
  {
  This routine is a modified version of TIB_Statement.GetFieldNamesList.
  Here we'll return field names as "<FieldName>" if possible.  Otherwise,
  the full entry "<RelName>.<FieldName>" will be returned.
  }
  procedure IBOGetSimplifiedFieldNamesList(
    ADataSet: TIB_DataSet;
    AStrings: TStrings );
  var
    TmpFullNames: TStringList;
    TmpSimpleNames: TStringList;
    TmpRepeatedNames: TStringList;
    TmpSimpleFieldName: string;
    ii: integer;
  begin
    try
      TmpFullNames := TStringList.Create;
      try
      TmpSimpleNames := TStringList.Create;
      try
      TmpRepeatedNames := TStringList.Create;
      try
        if AStrings <> nil then begin
          AStrings.BeginUpdate;
          try
            TmpFullNames.Clear;
            TmpSimpleNames.Clear;
            TmpRepeatedNames.Clear;
            AStrings.Clear;
            if Assigned(ADataSet) then begin
              {Get fieldname info from the dataset...}
              for ii := 0 to ADataSet.FieldCount - 1 do begin
                with ADataSet.Fields[ii] do begin
                  TmpFullNames.Add( UpperCase( FullFieldName ) );
                  TmpSimpleFieldName := UpperCase( FieldName );
                end;
                if TmpSimpleNames.IndexOf( TmpSimpleFieldName ) >= 0 then begin
                  TmpRepeatedNames.Add( TmpSimpleFieldName );
                end;
                TmpSimpleNames.Add( TmpSimpleFieldName );
              end;
              {Construct the fieldname list...}
              for ii := 0 to TmpFullNames.Count - 1 do begin
                if TmpRepeatedNames.IndexOf( TmpSimpleNames[ii] ) < 0 then begin
                  {Use simple name when it occurs only once...}
                  AStrings.Add( TmpSimpleNames[ii] );
                end else if not FHideRepeatedFields then begin
                  {Add repeated field name using it's full name...}
                  AStrings.Add( TmpFullNames[ii] );
                end;
              end;
            end;
          finally
            AStrings.EndUpdate;
          end;
        end;
      finally
        TmpRepeatedNames.Free;
      end;
      finally
        TmpSimpleNames.Free;
      end;
      finally
        TmpFullNames.Free;
      end;
    except
      Application.HandleException( Self );
    end;
  end;

begin
  if CreatingDefaultFields then begin
    {Grab list of fields from the dataset...}
    try
      CheckDataSetPrepared;
      if Assigned(FDataSet) then begin
          IBOGetSimplifiedFieldNamesList( FDataSet, AFieldNameList );
          Result := (AFieldNameList.Count > 0);
      end else begin
        AFieldNameList.Clear;
        Result := false;
      end;
    except
      AFieldNameList.Clear;
      Result := false;
    end;
  end else begin
    {Pull list from saved field objects...}
    Result := inherited GetFieldNames(AFieldNameList);
  end;
end;

function TppIBOPipeline.GetFieldDataType(AFieldName: string): TppDataType;
begin
  {Prepare/open dataset to allow access to required info...}
  if not Active then begin
    OpenDataSet;                  //<=-TppDBPipeline.GetFieldDataType does this!
    CheckDataSetPrepared;
  end;

  {Set the working column and get the data type...}
  SetWorkingColumn(AFieldName);
  Result := WorkingColumnDataType;
end;

function TppIBOPipeline.WorkingColumnDataType: TppDataType;
begin
  Result := dtNotKnown;

  if Assigned(FWorkingColumn) then begin
    with FWorkingColumn do begin
      //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      if IsCurrency then     //?RV playing with currency type
      begin
        Result := dtCurrency;
      end else
      if IsNumeric then begin  //...TIB_ColumnNumBase descendant
        if (FWorkingColumn is TIB_ColumnSmallInt) then begin
          {NOTE: RB uses dtInteger for smallint...}
          Result := dtInteger;
        end else if (FWorkingColumn is TIB_ColumnInteger) then begin
          Result := dtLongint;
        end else if (FWorkingColumn is TIB_ColumnNumeric) then begin
              Result := dtCurrency;
        end else if (FWorkingColumn is TIB_ColumnFloat) then begin
          Result := dtSingle;
        end else if (FWorkingColumn is TIB_ColumnDouble) then begin
          Result := dtDouble;
        end;
      //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      //WARNING// -- Check for IsBlob before checking for IsText
      end else
      if IsBlob then begin  //...TIB_ColumnBlob or descendant
        if IsText then begin  //...probably a TIB_ColumnMemo
          Result := dtMemo;
        end else begin
          Result := dtBLOB;
        end;
      //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      end else if IsText then begin  //...TIB_ColumnText or descendant
        Result := dtString;
      //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
      end else
      if IsDateTime then begin  //...TIB_ColumnDateTime
        if IsDateOnly then
          Result := dtDate
        else
        if IsTimeOnly then
          Result := dtTime
        else
          Result := dtDateTime;
      end else
        Result := dtString;
    end;
  end;
end;

function TppIBOPipeline.GetFieldAlignment(AFieldName: string): TAlignment;
begin
  if SetWorkingColumn(AFieldName) then begin
    Result := FWorkingColumn.Alignment;
  end else begin
    Result := taLeftJustify;
  end;
end;

{
Return the number of *characters* width for text control display.
Used to size text components when they are created.
NOTE: TppDBPipeline returns TField.DisplayWidth when
getting the result from the dataset.
}
function TppIBOPipeline.GetFieldDisplayWidth(AFieldName: string): integer;
var
  TmpField: TppField;
begin
  if AutoCreateFields then begin
    {Do not use TppField info when AutoCreateFields=true...}
    TmpField := nil;
  end else begin
    {Find TppField if one is available...}
    TmpField := GetFieldForName(AFieldName);
  end;

  if Assigned(TmpField) then begin
    {Use TppField's DisplayWidth...}
    Result := TmpField.DisplayWidth;
  end else if SetWorkingColumn(AFieldName) then begin
    {Get result from the dataset's info...}
    Result := FWorkingColumn.SQLLen;
  end else begin
    {Not found, so default to zero...}
    Result := 0;
  end;
end;

function TppIBOPipeline.GetFieldIsCalculated(AFieldName: string): boolean;
begin
  if SetWorkingColumn(AFieldName) then begin
    Result := FWorkingColumn.IsCalculated;
  end else begin
    Result := false;
  end;
end;

{
I'm not really sure what this is used for in the ReportBuilder code.
It's probably only used in TppTextPipeline and TppJITPipeline operation,
so is most likely not relevant here.

NOTE: TppDBPipeline returns TField.Size, which returns various values
depending on the field's type....

  ftString  	-=> max number of characters in the string
  ftVarBytes	-=> max number of bytes of data, not counting the first two bytes
    which indicate the actual number of bytes of data that were used.

  ftBlob	    -=> number of bytes from the BLOB stored in the record buffer
  ftFmtMemo  -=> number of bytes from the memo stored in the record buffer
  ftGraphic	-=> number of bytes from the image stored in the record buffer
  ftMemo	    -=> number of characters from the memo stored in the record buffer

  Most other types return zero (see delphi help for more info).
}
function TppIBOPipeline.GetFieldSize(AFieldName: string): integer;
begin
  Result := 0;
  if (not SetWorkingColumn(AFieldName)) then Exit;

  {Return field's "size" based on its type...}
  case WorkingColumnDataType of
    dtString:
      //NOTE: Should be *max* num chars, hopefully correct for CHAR column
      //  but will be different to BDE for VARCHAR columns.
      Result := FWorkingColumn.DataSize;
    dtMemo, dtBLOB:
      Result := FWorkingColumn.DataSize;

    //--THESE WILL RETURN ZERO, AS FOR TField.Size--
    //dtDate, dtTime, dtDateTime:
    //dtInteger, dtLongint:
    //dtDouble:
    //dtBoolean:
  end;
end;

function TppIBOPipeline.GetFieldValue(AFieldName: string): Variant;
begin
  Result := Null;

  if SetWorkingColumn(AFieldName) and (FWorkingColumn.IsNotNull) then begin
    //MODIFIED// CW 1999-05-28
    //
    //Result := FWorkingColumn.AsVariant;
    //
    case WorkingColumnDataType of
      dtString, dtChar:
        Result := FWorkingColumn.AsString;
      dtDouble, dtSingle, dtExtended:
        Result := FWorkingColumn.AsFloat;
      dtInteger, dtLongint:
        Result := FWorkingColumn.AsInteger;
      dtCurrency:
        Result := FWorkingColumn.AsCurrency;
      dtDate, dtTime, dtDateTime:
        Result := FWorkingColumn.AsDateTime;
      dtBoolean:
        Result := FWorkingColumn.AsBoolean;
    else
      Result := FWorkingColumn.AsVariant;
    end;
    //ENDMODIFIED//
    {Convert an Unassigned variant to Null if required...}
    if VarIsEmpty(Result) then begin
      Result := Null;
    end;
  end;
end;

function TppIBOPipeline.GetFieldAsDouble(AFieldName: string): Double;
begin
  if SetWorkingColumn(AFieldName) then begin
    Result := FWorkingColumn.AsDouble;
  end else begin
    Result := 0.0;
  end;
end;

function TppIBOPipeline.GetFieldAsPicture(AFieldName: string): TPicture;
begin
  Result := nil;

  if SetWorkingColumn(AFieldName) then begin
    if FWorkingColumn.IsBlob then begin
      //NOTE// "Result := TPicture(FWorkingColumn);" will not work with IBO,
      //  as the TIB_Column (or TIB_ColumnBlob) is not descended from
      //  TPersistent.  TPersistent defines Assign and AssignTo methods to
      //  allow such conversions.  ???
      //
      //  CW: emailed Digital Metaphors to suggest changing this method to
      //  pass in a TPicture reference to write the info to.
      //
      //  For now, just use a "temporary" instance of a TPicture to return the
      //  result.  This needs to be kept alive until after the call to this
      //  method, so it is created here if required and freed in the pipeline's
      //  destructor.
      //
      //  CW 1999-04-19
      //
      //TODO// REVISE THIS CODE IF ENANCEMENTS ARE MADE TO RBPRO
      //
      if not Assigned(FTemporaryPicture) then begin
        //NOTE// This object is freed in the pipeline's destructor
        FTemporaryPicture := TPicture.Create;
      end;
      if Assigned(FTemporaryPicture) then begin
        FWorkingColumn.AssignTo(FTemporaryPicture);
        Result := FTemporaryPicture;
      end;
    end;
  end;
end;

procedure TppIBOPipeline.GetFieldAsStream(AFieldName: string; AStream: TStream);
var
  TheValueString: string;
begin
  if SetWorkingColumn(AFieldName) then begin
    {Write the field's value to the stream...}
    if FWorkingColumn.IsBlob then begin
      FWorkingColumn.AssignTo(AStream);
    end else begin
      //TODO// -- CHECK THAT THIS CODE WORKS !!!
      TheValueString := FWorkingColumn.AsString;
      AStream.WriteBuffer( TheValueString, Length(TheValueString)+1 );
    end
  end;
end;

function TppIBOPipeline.GetFieldIsNull(aFieldName: String): Boolean;
begin
  if SetWorkingColumn(AFieldName) then begin
      Result := FWorkingColumn.IsNull;
  end else Result := true;
end;


function TppIBOPipeline.GetFieldAsString(AFieldName: string): string;
begin
  if SetWorkingColumn(AFieldName) then begin
    if FWorkingColumn.IsBlob then
      Result := FWorkingColumn.AsString
    else Result := FWorkingColumn.DisplayText;
  end else begin
    Result := '';
  end;
end;

//------------------------------------------------------------------------------
//TppIBOPipeline --  delete/edit/insert/post and searching routines required to
//                     save/load Report templates to database blob fields.
//------------------------------------------------------------------------------

procedure TppIBOPipeline.Delete;
begin
  if Assigned(FDataSet) then begin
    if Assigned(FOnBeforeDelete) then FOnBeforeDelete(self);
    FDataSet.Delete;
    if Assigned(FOnAfterDelete) then FOnAfterDelete(self);
  end;
end;

procedure TppIBOPipeline.Edit;
begin
  if Assigned(FDataSet) then begin
    if Assigned(FOnBeforeEdit) then FOnBeforeEdit(self);
    FDataSet.Edit;
    if Assigned(FOnAfterEdit) then FOnAfterEdit(self);
  end;
end;

procedure TppIBOPipeline.Insert;
begin
  if Assigned(FDataSet) then begin
    if Assigned(FOnBeforeInsert) then FOnBeforeInsert(self);
    FDataSet.Insert;
    if Assigned(FOnAfterInsert) then FOnAfterInsert(self);
  end;
end;

procedure TppIBOPipeline.Post;
begin
  if Assigned(FDataSet) then begin
    if Assigned(FOnBeforePost) then FOnBeforePost(self);
    FDataSet.Post;
    if RefreshAfterPost then FDataSet.RefreshAll;
    if Assigned(FOnAfterPost) then FOnAfterPost(self);
  end;
end;


{
Goto a IBO native bookmark for non-buffered datasets (TIB_Cursor).
Does a uni-directional brute-force search thru the dataset.
}
function TppIBOPipeline.NonBufferedGotoNativeBookmark(
  ADataSet: TIB_DataSet;
  ANativeBookmark: string): boolean;
var
  TmpOriginalBookmark: string;
begin
  Result := false;
  if not Assigned(ADataSet) or (Length(ANativeBookmark) = 0) then Exit;

  with FDataSet do begin
    TmpOriginalBookmark := Bookmark;
    DisableControls;
    try
      {Search for the required record...}
      First;
      while (not EOF) and (Bookmark <> ANativeBookmark) do begin
        Next;
      end;
      if (Bookmark = ANativeBookmark) then begin
        {Found it...}
        Result := true;
      end else begin
        {Try to return to the original record...}
        if (Bookmark <> TmpOriginalBookmark) then begin
          First;
          while (not EOF) and (Bookmark <> TmpOriginalBookmark) do begin
            Next;
          end;
        end;
      end;
    finally
      EnableControls;
    end;
  end;
end;

{
Perform locate for non-buffered datasets (TIB_Cursor)
NOTE: only supports single field locate at this time.
}
function TppIBOPipeline.NonBufferedLocate(
  ADataSet: TIB_DataSet;
  const AKeyFields: string;
  const AKeyValues: Variant;
  AOptions: TIB_LocateOptions): boolean;
var
  TheCol: TIB_Column;
  TheSearchText: string;
  TheSearchTextLength: Integer;
  TmpColumnText: string;
  TmpOriginalBookmark: string;
begin
  Result := false;
  if not Assigned(ADataSet) then Exit;

  {Get the column reference and the search text...}
  try
    TheCol := ADataSet.FieldByName( Trim(AKeyFields) );
    if (lopCaseInsensitive in AOptions) then begin
      TheSearchText := UpperCase( String(AKeyValues) );
    end else begin
      TheSearchText := String(AKeyValues);
    end;
    TheSearchTextLength := Length(TheSearchText);
  except
    TheCol := nil;
    TheSearchTextLength := 0;
  end;

  {Try to locate the record...}
  if Assigned(TheCol) and (TheSearchTextLength > 0) then begin
    with ADataSet do begin
      TmpOriginalBookmark := Bookmark;
      DisableControls;
      try
        if (lopPartialKey in AOptions) then begin
          {Search for text starting with TheSearchText...}
          First;
          while (not EOF) do begin
            TmpColumnText := TheCol.AsString;

            if (Length(TmpColumnText) >= TheSearchTextLength) then begin
              SetLength(TmpColumnText, TheSearchTextLength);
              if (lopCaseInsensitive in AOptions) then begin
                Result := UpperCase(TmpColumnText) = TheSearchText;
              end else begin
                Result := TmpColumnText = TheSearchText;
              end;
            end;

            if Result then Break;
            Next;
          end;
        end else begin
          {Search for text *matching* TheSearchText...}
          First;
          while (not EOF) do begin

            if (lopCaseInsensitive in AOptions) then begin
              Result := UpperCase(TheCol.AsString) = TheSearchText;
            end else begin
              Result := TheCol.AsString = TheSearchText;
            end;

            if Result then Break;
            Next;
          end;
        end;
        if not Result and (Bookmark <> TmpOriginalBookmark) then begin
          NonBufferedGotoNativeBookmark(ADataSet, TmpOriginalBookmark);
        end;
      finally
        EnableControls;
      end;
    end;
  end;
end;

function TppIBOPipeline.Locate(
  const AFieldName: string;
  AKeyValue: Variant;
  AOptions: TppLocateOptions): boolean;
var
  TheIBOptions: TIB_LocateOptions;
begin
  Result := False;
  if not Assigned(FDataSet) then Exit;

  {Make sure the dataset is open...}
  if not Active then begin
    OpenDataSet;
  end;

  //NOTE// TRUNCATION OF STRING KEYVALUES (AS FOR BDE) NOT REQUIRED WITH IBO

  {Translate the options to IBO options...}
  TheIBOptions := [];
  if (pploCaseInsensitive in AOptions) then Include(TheIBOptions, lopCaseInsensitive);
  if (pploPartialKey in AOptions) then Include(TheIBOptions, lopPartialKey);

  FDataSet.DisableControls;
  try
    {Go to the first record...}
    FDataSet.First;                  //<=-TppDBPipeline.Locate does this

    {Locate the record...}
    if (FDataSetIsBuffered) then begin
      Result := (FDataSet as TIB_BDataSet).Locate(AFieldName, AKeyValue, TheIBOptions);
    end else begin
      Result := NonBufferedLocate(FDataSet, AFieldName, AKeyValue, TheIBOptions);
    end;
  finally
    FDataSet.EnableControls;
  end;
end;

procedure TppIBOPipeline.SetFieldFromStream(AFieldName: string; AStream: TStream);
begin
  if SetWorkingColumn(AFieldName) then begin
    {Set the field's value from the stream...}
    if FWorkingColumn.IsBlob then begin
      FWorkingColumn.Assign(AStream);
    end else begin
      //TODO// -- SEE IF OTHER COLUMN TYPES NEED TO BE SUPPORTED HERE.
      //            TppDBPipeline.SetFieldFromStream ONLY DOES BLOBS.
      //
      //Assert(false, 'TppIBOPipeline.SetFieldFromStream: Field type not supported');
      FWorkingColumn.Assign(AStream);  //<=-will throw an exception if not supported
    end
  end;
end;

procedure TppIBOPipeline.SetFieldValue(AFieldName: string; AValue: Variant);
begin
  if SetWorkingColumn(AFieldName) then begin
    FWorkingColumn.AsVariant := AValue;
  end;
end;

// Override to apply alias information...
procedure TppIBOPipeline.CreateDefaultFields;
var
  liField: Integer;
begin
  if FRecursionFlag = 0 then  // << ADD
    try                       // << ADD
      Inc(FRecursionFlag);    // << ADD
        // These tests copied from derivation to avoid processing when not wanted...
  if (CreatingDefaultFields) then Exit;
  if not(AutoCreateFields) then Exit;
  if not(FieldsOutOfSync) then Exit;
  if (csReading in ComponentState) or (csLoading in ComponentState) or (csDestroying in ComponentState) then Exit;
  inherited;

  for liField := 0 to FieldCount - 1 do begin
      SetWorkingColumn( Fields[liField].FieldName );
      Fields[liField].FieldAlias   := FWorkingColumn.DisplayName;
  end;

    finally                   // << ADD
      Dec(FRecursionFlag);    // << ADD
    end;                      // << ADD
end; //procedure, CreateDefaultFields


//==============================================================================
//==============================================================================

//------------------------------------------------------------------------------
//TppChildIBOPipeline
//------------------------------------------------------------------------------

function TppChildIBOPipeline.HasParent: boolean;
begin
  Result := true;
end;

//------------------------------------------------------------------------------
//TppChildIBODataSource
//------------------------------------------------------------------------------

function TppChildIBODataSource.HasParent: boolean;
begin
  Result := true;
end;

//------------------------------------------------------------------------------
//REGISTER CLASSES...
//------------------------------------------------------------------------------

initialization
  RegisterClasses([TppIBOPipeline, TppChildIBOPipeline, TppChildIBODataSource]);

finalization
  UnRegisterClasses([TppIBOPipeline, TppChildIBOPipeline, TppChildIBODataSource]);

//------------------------------------------------------------------------------

end.

