unit ULObjUsru;{v0.22}
{ see ULObjUsrTmpu for template for ulobjusr descendant }
{ Main Usr of ULObj data object (and its childs) ulanrecs.lst }

interface
uses
  {$IFNDEF CONSOLE}
  Windows, Messages, Dialogs, Msgu, Forms, ULStringGrid,
  {$ELSE}
  Msgu,
  {$ENDIF}
  Classes, SysUtils,
  UtlType, ULRecTyp, ULRecUtl, ULObju, {v0.37}ULObjDes,{/v0.37}
  ULFObju{v0.28}, Language{/v0.28}{v0.41}, WinUtl{/v0.41}
  {v0.47},ExeLogu{/v0.47};{ulbrowsefrm}

const
  SaveULObjUsrAlsoToASC: boolean = true;

type
  TULObjUsrsList = class;
  {v0.61}
  TULObjUsr = class;
  TULObjUsrClass = class of TULObjUsr;
  {/v0.61}

  TULObjUsr = class({v0.23}TULObjBasicUsr{/v0.23 TObject ulobju})
  private
    {v0.23}{/v0.23
    FObj: TULObj; { the source of data for this object }
    {v0.23}{/v0.23
    FRegistered: boolean;}{TULObjUsr registered at FObj as User?}
    FOwner: TULObjUsr;
      { if has owner, if not (=nil) than this is root and will be saved
        to FULF file }
    FULF: TULFObj;
    FChilds: TULObjUsrsList;{ if has child objusrs }
    {v0.23}
    FControlChilds: boolean;
      { should the childs of FObj be watched and ULObjUsr objects created
        for them in WMAppMessage ? }
    FDefRecID: TULRecID;{ the one submitted to Create method }
    {/v0.23}
    {v0.24}
    FCreatingChildObj: boolean;
    {/v0.24}
    {v0.36}
    {FCurULGrid: TULStringGrid;}
    {/v0.36}
    {v0.44}
    FLoadingFromFile: boolean;
    FInDestroy: boolean;
    {v0.46}
    {$IFNDEF CONSOLE}
    FActiveGrid: TULStringGrid;
    {$ENDIF}
    {/v0.46
    FGridActive: boolean;}
    {/v0.44}
    {v0.60}
    FNextChildInsertIndex: integer;
      { where should be next created child inserted in ChildList
        (at the end for -1; default). Set to something before
        calling ChildAdd. Gets reset back to -1 after ChildAdd call. }
    {/v0.60}
    {v0.61}
    { Can be assigned to in overriden DoBeforeCreate method,
      if the this class has only one allowed child.
      Then ChildCreate does not have to be overriden. }
    FChildClass: TULObjUsrClass;
    {/v0.61}
  protected
    {function GetOwnerList: TULObjUsrsList;}
    {v0.31}
    {$IFNDEF CONSOLE}
    procedure WMAppMessage(var Msg:TMessage); message WM_APPMESSAGE;
    {$ENDIF}
    {/v0.31}
    {v0.23}
    {/v0.23
    procedure WMAppMessage(var Msg:TMessage); message WM_APPMESSAGE;
    procedure ChildObjDestroyed(AChildObj: TULObj)}
    function GetChildList: TULObjUsrsList;
    function GetChild(Index: integer):TULObjUsr;
    function GetChildCount: integer;

    { Override if should have childs:
         Result := TULObjUsrXXX.Create(AChildObj,xxx,..);
      if overriden method returns nil, no ULObjUsr child for the AChildObj
      will be created.
      Does not have to be overriden if FChildClass property is assinged.
      If the metod is not overriden and FChildClass is not assigned,
      calling it will raise exception. }
    function ChildCreate(AChildObj: TULObj): TULObjUsr; virtual;
    procedure ChildsUpdate; {v0.47}virtual;{/v0.47}{ulantype}
    {v0.24}
    {procedure SetObj(AObj: TULObj); override;}
      { Calls inherited, then ClassFieldsUpdate.
        If ClassFieldsUpdate method should not be called, use
        Obj.Assign (or XXXX.Assign, if Obj has typecasting method),
        then call ClassFieldsUpdate (when needed) explicitely }
    {/v0.24}

    {v0.23}
    procedure ObjUpdated; override;
      { called when Obj changed (or it's child count changed) }

    procedure ClassFieldsCreate; virtual;
      { Called from Create and LoadFromFile. Descendants can override to create
        (update) some class fields (or change other values) that depend on info
        loaded from file. ClassFieldsDestroy is called from LoadFrom file
        before actual loading.
        Call inherited as first line in overriden methods!!!. }
    procedure ClassFieldsDestroy; virtual;
      { Should destroy all what was created in ClassFieldsCreate method
        and set pointers to nil. Call inherited as last first line in overriden methods. }
    function GetFileName: string;
    {/v0.23}
    {v0.24}
    {v0.61} class function GetClassRecID: TULRecID; virtual; abstract;{/v0.61}
    function GetRecID: TULRecID; virtual; {v0.61}{/v0.61 abstract;}
      { must be overriden by descendants that use Load constructor }
    procedure SetFileName(const AFileName: string);
    procedure UpdateDefDirs; virtual;
      { Does nothing hear. Can be overriden to set FldDescs.DefDir
        accordingly to Obj.DefDir. Not called automatically from anywhere. }
    procedure DoCreate(AOwner: TULObjUsr; AObj: TULObj; ARecID: TULRecID; FromLoad:boolean);
    {v0.28}
    procedure DoAfterCreate; virtual;
      { Called after DoCreate; Does nothing (but call INHERITED from first line)
        Useful (if Load constructor used) to change defaults
        (like ControlChilds), before loading from file }
    {/v0.28}
    {v0.44}
    procedure DoBeforeCreate; virtual;
      { Good place to set default (non zero) runtime values. Call inherited. }
    procedure DoAfterLoad; virtual;
    {$IFNDEF CONSOLE}
    procedure ULGridFormActivated(AGrid: TULStringGrid); virtual;
      { updates GridActive flag, override to perforem action upon activating browser }
    procedure ULGridFormDeactivated(AGrid: TULStringGrid); virtual;
    {$ENDIF}
    {/v0.44}
    {v0.47}
    {procedure DoFileNotFound; virtual;}
      { called if file with name submitted to Load constructor not found;
        does nothing by default; }
    {/v0.47}

    {v0.45}

    procedure AfterBrowseChildInsert; override;

    procedure AfterBrowseInsert; virtual;
      { called from owner.AfterBrowseChildInsert; i.e. called if this object
        was just inserted by user }
    {/v0.45}
    {v0.46}
    {$IFNDEF CONSOLE}
    function GetActiveGridForm: TForm; virtual;
    {$ENDIF}
    {/v0.46}


    {v0.38}
    procedure ChildDestroyed(AChild: TULObjUsr); virtual;
      { called at the begging of Destroy of child for FOwner; gives a chance
        to clear additional pointers to some childs }
    {/v0.24}

    {v0.30}
    {procedure MenuActionNeeded; virtual; see ULObjBasicUsr}
      { should be overriden if the user wants to add action to local menu,
        should create (and own) TAction object (just once, and keep its pointer)
        and call Obj.MenuActionAdd method in this method.
        TAction.OnExecute should be TNotifyEvent of user. }
    {/v0.30}
    {v0.32}
    procedure SetULF(AULF: TULFObj);
    procedure FileNameChanged;virtual;
    {/v0.32}{ulrectyp}
    {v0.36}
    {$IFNDEF CONSOLE}
    procedure ULGridCreated(AGrid: TULStringGrid); virtual;
    {$ENDIF}
    {/v0.36}
    {v0.41}
    function GetFullFileName: string;
    procedure SetFullFileName(const AFileName: string);
    function GetRelFileName: string;
    {/v0.41}
    {v0.45}
    function GetActiveChild: TULObjUsr;
    procedure SetULObjOwner(AOwner: TULObjUsr); virtual;
      { can be used to change owner after creation of this obj }
    {/v0.45}
    {v0.47}
    function GetCaption: string; virtual;
      { returns string, that should appear in treeview for this ulobjusr node }
    {/v0.47}
    {v0.70}
    { override and return false if wanted some values ommited from combobox
      offering possible values for ULEnum field }
    function AddingULEnumFieldValue(AULObjField: TULObjField): boolean; virtual;
    {/v0.70}
    procedure Sort;
  public
    { AObj or AULRecID must be empty, not both. If AOwner empty, then this
      is the root ULObjUsr, that will perform saving to file and ARecID must
      be non empty (i.e. AObj nil - could not be created yet without any owner) }
    constructor Create(AOwner: TULObjUsr; AObj: TULObj; ARecID: TULRecID); reintroduce;
    {v0.24}
    constructor Load(const AFileName: string); {v0.41} virtual;{/v0.41}
    {/v0.24}
    procedure ClassFieldsUpdate;
      { calls ClassFieldsDestroy, ClassFieldsCreate;
        should be called if Obj assigned(called automatically),
        FileName assigned, ... }
    destructor Destroy; override;
    function ChildAdd(AChildObj: TULObj; ARecID: TULRecID; const ARecName: string): TULObjUsr;
      { calls ChildCreate with AChildObj if non nil,
        if AChildObj is nil, then creates new one first (with AULRecID)
        then call ChildCreate }
    function ChildFind(AChildObj: TULObj; var AChild: TULObjUsr): boolean;
    {v0.24}
    function ChildFindOrAdd(ARecID: TULRecID; const AName: string): TULObjUsr;
    {/v0.24}
    {v0.24}
    procedure FileNew; virtual;
      { clears data, set file name to 'NONAME' + RecIDExt; called from Load
        if AFileName = '' }
    {$IFNDEF CONSOLE}
    function FileOpen: boolean;
      { Opens OpenDialog, gives a chance to select existing file with
        RecID corresponding extension, using Obj.Caption as file description,
        if returns true, then the content of the file assigned to Obj (i.e.
        replaced its current content) using LoadFromFile method. }
    function FileSaveAs: boolean;
      { Opens SaveDialog, gives a chance to save the content of the Obj to
        the file, as default ext offered RecID ext, description = Obj.Caption.
        If SaveDialog returns true, then the Obj saved using SaveToFile method. }
    {$ENDIF}   
    {/v0.24}
    {v0.23}
    procedure LoadFromFile(const AFileName: string);virtual;
    procedure SaveToFile(const AFileName: string);
    {v0.25}
    procedure Save; virtual;
    {/v0.25}

    procedure Browse; virtual;
    function Edit: integer;
    function EditModal: integer;
    {/v0.23 property Obj: TULObj read FObj;}
    {v0.44}
    procedure Clear; virtual;
    procedure SaveIfNotVisible;
      { can be called before calling Browse, so that the user is not asked
        to save changes he did not make }
    {/v0.44}
    {v0.47}
    procedure Log(const msg: string); virtual;
    procedure LogErr(const msg: string); virtual;
    {/v0.47}
    {v0.64}
    { Calls Obj.Assign(Source), then calls ClassFieldsUpdate. Can be overriden
      to do eventual further changes necessary after assigning content of other
      ULObj. Call inherited first. }
    procedure ObjAssign(Source: TULObj); virtual;
    { For Source TULObjUsr calls ObjAssign else inherited }
    procedure Assign(Source: TPersistent); override;
    {/v0.64}


    {v0.23}
    property ControlChilds: boolean read FControlChilds write FControlChilds;
    property FileName: string read GetFileName write SetFileName;
    {/v0.23}
    property ChildList: TULObjUsrsList read GetChildList;
    property Childs[Index: integer]:TULObjUsr read GetChild;
    property ChildCount: integer read GetChildCount;
    {v0.32}
    property ULF: TULFObj read FULF write SetULF;
    {/v0.32}{ulobju}
    {v0.41}
    property FullFileName: string read GetFullFileName write SetFullFileName;
    property RelFileName: string read GetRelFileName;
    {/v0.41}
    {v0.44}
    property LoadingFromFile: boolean read FLoadingFromFile write FLoadingFromFile;
    property InDestroy: boolean read FInDestroy write FInDestroy;
    property ULObjOwner: TULObjUsr read FOwner {v0.45} write SetULObjOwner{/v0.45};
    {v0.46}
    {$IFNDEF CONSOLE}
    property ActiveGrid: TULStringGrid read FActiveGrid;
    property ActiveGridForm: TForm read GetActiveGridForm;
    {$ENDIF}
    {/v0.46
    property GridActive: boolean read FGridActive;}
    {/v0.44}
    {v0.45}
    property ActiveChild: TULObjUsr read GetActiveChild;
    {/v0.45}
    {v0.47}
    property Caption: string read GetCaption;
    {/v0.47}
    {v0.61}
    property ChildClass: TULObjUsrClass read FChildClass write FChildClass;
    {/v0.61}
  end;
  {v0.41}
  {v0.61 moved above}{/v0.61
  TULObjUsrClass = class of TULObjUsr;}
  {/v0.41}

  TULObjUsrsList = class(TList)
    { descendants of this list should be used as holders of Usrs of ULObj objects
      = TULObjUsr descentant, i.e. every Item of the list is object that have
      assigned just one ULObject (child of FObj field) }
  private
    FObjUsr: TULObjUsr; { owner of childs, objects in this list }
  public
    constructor Create(AObjUsr: TULObjUsr); reintroduce;
    destructor Destroy; override;
    {procedure UpdateChilds;}
  end;

{v0.41}
function ULObjUsrBrowserOpen(AULObjUsrClass: TULObjUsrClass; ARecID: TULRecID; const AFileName: TFileName;
  AMode: TOpenMode): TULObjUsr;{ulantype}
{/v0.41}

{v0.61}
{ Makes sure that instance of TULObjUsr descendant (class AULObjUsrClasss)
  - AULObjUsr of is initialized. If AULObjUsr is nil, creates the instance
  by loading it from the AFileName, if is non nil, just returns true. }
function ULObjUsrGet(AULObjUsrClass: TULObjUsrClass; const AFileName: string;
  var AULObjUsr: TULObjUsr): boolean;
{/v0.61}

implementation
{v0.37}{/v0.37
const
  FOpenDialog: TOpenDialog = nil;
  FSaveDialog: TSaveDialog = nil;}

{TULObjUsr}
constructor TULObjUsr.Create(AOwner: TULObjUsr; AObj: TULObj; ARecID: TULRecID);
{var
  fn:string;}
begin
  inherited Create(nil);
  {v0.60}
  FNextChildInsertIndex := -1;
  {/v0.60}
  DoCreate(AOwner, AObj, ARecID, false);
  (*
  FOwner := AOwner;
  {v0.24}
  if FOwner <> nil then begin
    FOwner.Obj.DoChangeLock;
  end;
  try
  {/v0.24}
    {v0.23}
    FDefRecID := ARecID;
    FControlChilds := true;
    {/v0.23}
    if FOwner = nil then begin
      if AObj <> nil then begin
        {v0.24}
        SetResult(urInvalidCreateParams,'(AObj <> nil) and (AOwner = nil)');
        {/v0.24
          raise Exception.Create('TULObjUsr.Create (AObj <> nil) and (AOwner = nil)');}
      end;
      if ARecID = 0 then begin
        {v0.24}
        SetResult(urInvalidCreateParams,'(RecID = 0) and (AOwner = nil)');
        {/v0.24
        raise Exception.Create('TULObjUsr.Create (RecID = 0) and (AOwner = nil)');}
      end;
      FULF := TULFObj.Create(nil);
      {v0.23}
      fn := ULRecDefFileName(ARecID);
      {/v0.23
      fn := ULRecIDToStrStrip(ARecID);
      fn := fn + '.' + copy(fn,1,3);}
      FULF.FileName := fn;
      if FileExists(fn) then
      try
        FULF.LoadFromFile(fn);
        {v0.24}FULF.JustLoadedFromFile := true;{/v0.24}
      except
        { do nothing}
      end;
      {v0.23}
      Obj {/v0.23 FObj} := FULF.FindOrAdd(ARecID, '');
    end else begin
      if AObj = nil then begin
        FCreatingChildObj := true;
        try
          AObj := FOwner.Obj.Add(ARecID);
        finally
          FCreatingChildObj := false;
        end;
      end;
      {v0.23} Obj {/v0.23 FObj} := AObj;
      if (ARecID <> 0) and (ARecID <> AObj.RecID) then begin
        {v0.24}
        SetResult(urInvalidCreateParams, 'AObj.RecID <> ARecID)');
        {/v0.24
        raise Exception.Create('TULObjUsr.Create (AObj.RecID <> ARecID)');}
      end;
      {v0.23}
      FDefRecID := Obj.RecID;
      {/v0.23}
    end;

    {FObj.SetFlag(rfCantDelete, true);}
    {v0.23}{/v0.23
    FObj.UserRegister(Self);
    FRegistered := true;}
    if FOwner <> nil then begin
      FOwner.ChildList.Add(Self);
    end;
    {v0.23}
    ClassFieldsCreate;
    {/v0.23}
  {v0.24}
  finally
    if FOwner <> nil then begin
      FOwner.Obj.DoChangeUnlock;
    end;
  end;
  {/v0.24}
  *)
end;

{v0.24}
procedure TULObjUsr.DoCreate(AOwner: TULObjUsr; AObj: TULObj; ARecID: TULRecID; FromLoad:boolean);
var fn: string;
begin
  FOwner := AOwner;
  if FOwner <> nil then begin
    {v0.47}
    if FOwner.Obj <> nil then
    {/v0.47}
      FOwner.Obj.DoChangeLock;
  end;
  {v0.44}
  DoBeforeCreate;
  {/v0.44}
  try
    FDefRecID := ARecID;
    FControlChilds := true;
    if FOwner = nil then begin
      if AObj <> nil then
        SetResult(urInvalidCreateParams,'(AObj <> nil) and (AOwner = nil)');
      if ARecID = 0 then
        {v0.45
          allowed ulobjusr without Obj (just keeper of other ULObjUsrs),
          must override GetRecID and ChildCreate to do nothing = return 0/nil}
        {/v0.45
        SetResult(urInvalidCreateParams,'(RecID = 0) and (AOwner = nil)');}
      {v0.45} else {/v0.45}
      begin
        {v0.32}ULF{/v0.32 FULF} := TULFObj.Create(nil);
        if not FromLoad then begin
          fn := ULRecDefFileName(ARecID);
          ULF.FileName := fn;
          if FileExists(fn) then
          try
            ULF.LoadFromFile(fn);
            ULF.JustLoadedFromFile := true;
          except
          end;
        end;
        Obj := ULF.FindOrAdd(ARecID, '');
      end;
    end else begin
      if AObj = nil then begin
        {v0.47}
        if ARecID <> 0 then
        {/v0.47}
        begin
          FCreatingChildObj := true;
          try
            AObj := FOwner.Obj.Add(ARecID);
          finally
            FCreatingChildObj := false;
          end;
        end;
      end;
      Obj := AObj;
      if (ARecID <> 0) and (ARecID <> AObj.RecID) then
        SetResult(urInvalidCreateParams, 'AObj.RecID <> ARecID)');
      {v0.47}
      if Obj <> nil then
      {/v0.47}
        FDefRecID := Obj.RecID;
    end;

    if FOwner <> nil then begin
      {v0.60}
      if FOwner.FNextChildInsertIndex > -1 then begin
        FOwner.ChildList.Insert(FOwner.FNextChildInsertIndex, Self);
        FOwner.FNextChildInsertIndex := -1;
      end else
      {/v0.60}
      FOwner.ChildList.Add(Self);
    end;
    if not FromLoad then
      ClassFieldsCreate;
  finally
    if FOwner <> nil then begin
      {v0.47}
      if FOwner.Obj <> nil then
      {/v0.47}
        FOwner.Obj.DoChangeUnlock;
    end;
  end;
  {v0.28}
  DoAfterCreate;
  {/v0.28}
end;
{/v0.24}

{v0.44}
procedure TULObjUsr.DoBeforeCreate;
begin
end;
{/v0.44}

{v0.28}
procedure TULObjUsr.DoAfterCreate;
begin
end;
{/v0.28}

constructor TULObjUsr.Load(const AFileName: string);
begin {ulobju}
  inherited Create(nil);
  DoCreate(nil, nil, GetRecID, true);
  if AFileName <> '' then begin
    if (not FileExists(AFileName)) then begin
      if (ExtractFileName(AFileName) <> ULRecDefFileName(GetRecID)) then begin
        if ShowMessage(GetTxt({#}'File') + ' ' + AFileName + ' ' + GetTxt({#}'not found. Create?'), smNoYes, 0) <> cmYes then begin
          SetResult(urFileNotFound, AFileName);
        end;
      end;
      FileName := AFileName;
      ClassFieldsCreate;
      {v0.47}
      {DoFileNotFound;}
      {/v0.47}
      {v0.29}
      {Save;?? ulrectyp}
      {/v0.29}
    end else begin
      try
        LoadFromFile(AFileName);
      except
        if ShowMessage(GetTxt({#}'File') + ' ' + AFileName + ' ' + GetTxt({#}'corrupted. Ignore?'), smNoYes, 0) <> cmYes then begin
          raise;
        end;
        ClassFieldsCreate;
      end;
    end;
  end else begin
    {v0.32}
    FileNew;
    {/v0.32
    ClassFieldsCreate;}
  end;
  {v0.44}
  DoAfterLoad;
  {/v0.44}
  {v0.24}
  {/v0.24
  Create(nil, nil, GetRecID); // TApplication
  if AFileName <> '' then begin
    if (FULF = nil) or (not FULF.JustLoadedFromFile) or (AFileName <> FULF.FileName) then begin
      if (AFileName <> '') and (AFileName <> ULRecDefFileName(GetRecID)) then begin
        if not FileExists(AFileName) then begin
        end;
      end;
      LoadFromFile(AFileName);
    end;
    FULF.JustLoadedFromFile := false;
  end;
  }
end;

{v0.44}
procedure TULObjUsr.DoAfterLoad;
begin
end;
{/v0.44}

function TULObjUsr.GetChild(Index: Integer):TULObjUsr;
begin
  Result := TULObjUsr(FChilds.Items[Index]);
end;

function TULObjUsr.GetChildCount: integer;
begin
  Result := ChildList.Count;
end;

function TULObjUsr.GetChildList: TULObjUsrsList;
begin
  if FChilds = nil then
    FChilds := TULObjUsrsList.Create(Self);
  Result := FChilds;
end;
{v0.23}

procedure TULObjUsr.ObjUpdated;
begin
  if ControlChilds then begin
    {v0.47}
    if Obj <> nil then
    {/v0.47}
    begin
      if (ChildCount <> Obj.ChildCount) or (Obj.ChangedCount > 1) then begin
        if not FCreatingChildObj then
          ChildsUpdate;
      end;
    end;
  end;
end;
{/v0.23
procedure TULObjUsr.WMAppMessage(var Msg:TMessage);
begin
  case Msg.wParam of uledfrm
    cmULObjUpdated: begin
      if TULObj(Msg.lParam) = FObj then begin
        if ChildCount <> FObj.ChildCount then
          ChildsUpdate;
      end;
    end;
    cmULObjDestroyed: begin
      if TULObj(Msg.lParam) = FObj then begin
        Free;
      end;
    end;
  end;
end; }

{v0.32}
procedure TULObjUsr.SetULF(AULF: TULFObj);
begin
  if FULF <> nil then begin
    FULF.UserUnregister(Self);
  end;
  FULF := AULF;
  if FULF <> nil then begin
    FULF.UserRegister(Self);
    {v0.41}
    if Obj <> nil then
      Obj.FileName := FULF.FileName;
    {/v0.41}
  end;
end;

procedure TULObjUsr.FileNameChanged;
begin

end;
{/v0.32}

{v0.31}
{$IFNDEF CONSOLE}
procedure TULObjUsr.WMAppMessage(var Msg: TMessage);
begin
  if (Msg.lParam = longint(ULF)) then begin
    case Msg.wParam of
      cmULObjFileNameChanged: FileNameChanged;
      {v0.46}
      cmULObjDestroyed: begin
        {!!!???}
        {Obj.UsersNotify(cmULObjDestroyed);}
        Obj := nil;
        ULF := nil;
        Free;
        exit;
      end;
      {/v0.46}
    end;
  end else if (Msg.lParam = longint(Obj)) then begin
    case Msg.wParam of
      cmULObjFileNameChanged: FileNameChanged;
      cmULObjULGridDestroyed: begin
        if Obj.BrowserAutoClose then begin
          {v0.47}
          {if ULF <> nil then
            ULF.Free
          else
            Obj.Free;}
          {/v0.47
          Free;}
        end;
      end;
      {v0.36}
      cmULObjULGridCreated: begin
        ULGridCreated(CurULStringGrid);
      end;
      {/v0.36}
      {v0.44}{ulrectyp}
      cmULObjULGridFormActivated: begin
        ULGridFormActivated(CurULStringGrid);
      end;
      cmULObjULGridFormDeactivated: begin
        ULGridFormDeactivated(CurULStringGrid);
      end;
      {/v0.44}
      cmULObjChildsResorted: begin
        Sort;
      end;
    end;
  end{v0.70} else begin
    case Msg.wParam of
      cmULObjAddingULEnumFieldValue: begin
        if not AddingULEnumFieldValue(TULObjField(msg.LParam)) then
          Msg.Result := -1;
      end;
    end;
  end {/v0.70};
  inherited;
end;
{$ENDIF}
{/v0.31}

function TULObjUsr.ChildCreate(AChildObj:TULObj): TULObjUsr;
begin
  {v0.61}
  if Assigned(FChildClass) then begin
    Result := FChildClass.Create(Self, AChildObj, FChildClass.GetClassRecID);
    exit;
  end;
  {/v0.61}
  {v0.46}
  SetResult(urChildCreateNotOverriden, '');{ulrectyp}
  Result := nil;
  {/v0.46 Result := TULObjUsr.Create(Self, AChildObj, AChildObj.RecID);}
end;

function TULObjUsr.ChildAdd(AChildObj: TULObj; ARecID: TULRecID; const ARecName: string): TULObjUsr;
begin
  if (AChildObj = nil) {v0.47} and (Obj <> nil){/v0.47} then begin
    {v0.24}
    FCreatingChildObj := true;
    try
    {/v0.24}
      AChildObj := Obj.Add(ARecID);
    {v0.24}
    finally
      FCreatingChildObj := false;
    end;
    {{/v0.24}
    if ARecName <> '' then
      AChildObj.RecName := ARecName;
  end;
  Result := ChildCreate(AChildObj);
end;

{v0.24}
procedure TULObjUsr.ChildDestroyed(AChild: TULObjUsr);
begin
end;

function TULObjUsr.ChildFindOrAdd(ARecID: TULRecID; const AName: string): TULObjUsr;
var i: integer;
begin
  for i := 0 to ChildCount - 1 do begin
    if Childs[i].Obj.RecID = ARecID then begin
      if AName <> '' then begin
        if Childs[i].Obj.RecName = AName then begin
          Result := Childs[i];
          exit;
        end;
      end else begin
        Result := Childs[i];
        exit;
      end;
    end;
  end;
  Result := ChildAdd(nil, ARecID, AName);
end;
{/v0.24}

function TULObjUsr.ChildFind(AChildObj: TULObj; var AChild: TULObjUsr): boolean;
var i: integer;
begin
  AChild := nil;
  Result := false;
  if Obj = nil then
    exit;
  for i := 0 to ChildCount - 1 do begin
    if Childs[i].Obj = AChildObj then begin
      AChild := Childs[i];
      Result := true;
    end;
  end;
end;

procedure TULObjUsr.ChildsUpdate;
var
  o: TULObj;
  i: integer;
  ou: TULObjUsr;
begin
  if Obj = nil then
    exit;
  for i := 0 to Obj.ChildCount - 1 do begin
    o := TULObj(Obj.Childs[i]);
    if not ChildFind(o, ou) then begin
      {v0.60}
      FNextChildInsertIndex := i;
      {/v0.60}
      ChildAdd(o, 0, '');
    end;
  end;
  {v0.24}
  {v0.64}
  i := 0;
  while i < ChildCount do begin
    ou := Childs[i];
    if Obj.ChildList.IndexOf(ou.Obj) < 0 then begin
      ou.Free; { ...decreases ChildCount }
    end else begin
      inc(i);
    end;
  end;
  {/v0.64
  for i := 0 to ChildCount - 1 do begin
    ou := Childs[i];
    if Obj.ChildList.IndexOf(ou.Obj) < 0 then
      ou.Free;
  end;}
  {/v0.24}
end;

destructor TULObjUsr.Destroy;
var
  l: TULObjUsrsList;
  i: integer;
  {v0.32}
  ff: TULFObj;
  {/v0.32}
begin
  {v0.44}
  FInDestroy := true;
  {/v0.44}
  {v0.24}
  if FOwner <> nil then
    FOwner.ChildDestroyed(Self);
  {/v0.24}
  {v0.23}
  ClassFieldsDestroy;
  Obj := nil;
  {/v0.23
  if FObj <> nil then begin
    if FRegistered then
      FObj.UserUnregister(Self);
  end;}
  if FOwner <> nil then begin
    l := FOwner.ChildList;
    if l <> nil then begin
      i := l.IndexOf(Self);
      if i >= 0 then
        l.Delete(i);
    end;
  end else begin
    if ULF <> nil then begin
      if ULF.Modified {v0.24} or (not FileExists(ULF.FileName)) {/v0.24} then begin
        if (ULF.FileName = '') {or  (pos('NONAME', FULF.FileName) > 0) }then begin

        end else begin
          ULF.SaveToFile(ULF.FileName);
        end;
      end;
      if SaveULObjUsrAlsoToASC then begin
        if ULF.FileName <> '' then
          ULF.SaveToFile(ChangeFileExt(ULF.FileName, AscExt));
      end;
        { ... just for debugging }
      {v0.24 free after destroying ulobjusr childs}
      {/v0.24
      FULF.Free;}
    end;
  end;
  FChilds.Free;
  {v0.24}
  if FULF <> nil then begin
    {v0.32}
    ff := FULF;
    ULF := nil;
    ff.Free;
    {/v0.32
    FULF.Free;
    FULF := nil;}
  end;
  {/v0.24}
  inherited Destroy;
end;

{v0.45}
procedure TULObjUsr.SetULObjOwner(AOwner: TULObjUsr);
var
  l: TULObjUsrsList;
  i: integer;
begin
  if FOwner <> nil then begin
    l := FOwner.ChildList;
    if l <> nil then begin
      i := l.IndexOf(Self);
      if i >= 0 then
        l.Delete(i);
    end;
  end;
  FOwner := AOwner;
  if FOwner <> nil then begin
    if FOwner.Obj <> nil then
      FOwner.Obj.DoChangeLock;
    try
      FOwner.ChildList.Add(Self);
    finally
      if FOwner.Obj <> nil then
        FOwner.Obj.DoChangeUnlock;
    end;
  end;
end;
{/v0.45}

{v0.23}
procedure TULObjUsr.ClassFieldsDestroy;
begin
end;

procedure TULObjUsr.ClassFieldsCreate;
begin
  ChildsUpdate;
end;

procedure TULObjUsr.ClassFieldsUpdate;
begin
  ClassFieldsDestroy;
  ClassFieldsCreate;
end;

procedure TULObjUsr.LoadFromFile(const AFileName: string);
var
  f:TULFObj;
  o:TULObj;
{v0.41}
  fn: string;
{/v0.41}
begin
  f := TULFObj.Create(nil);
  {v0.44}
  LoadingFromFile := true;
  {/v0.44}
  try
    {v0.41}
    fn := AFileName;
    if fn = '' then
      fn := FullFileName;
    f.LoadFromFile(fn);
    {/v0.41
    f.LoadFromFile(AFileName);}
    o := f.FindOrAdd(FDefRecID, '');
    ClassFieldsDestroy;
    FCreatingChildObj := true;
    try
      Obj.Assign(o);
      {v0.26}
      {v0.58}{/v0.58
      Obj.Modified := false;}
      {/v0.26}
    finally
      FCreatingChildObj := false;
    end;
    ClassFieldsCreate;
    {v0.58}
    Obj.Modified := false;
    {/v0.58}
  finally
    {v0.44}
    LoadingFromFile := false;
    {/v0.44}
    {v0.24}
    if (Owner = nil) then begin
      if (FULF = nil) then begin
        {v0.34}
        ULF := f
        {/v0.34
        FULF := f}
      end else begin
        FULF.FileName := {v0.41}fn{/v0.41 AFileName};
        f.Free;
      end;
    end else
    {/v0.24}
      f.Free;
  end;
end;

{v0.25}
procedure TULObjUsr.Save;
begin
  SaveToFile('');
end;
{/v0.25}

procedure TULObjUsr.SaveToFile(const AFileName: string);
begin {ulobju}
  if FULF = nil then begin
    Obj.SaveToFile(AFileName);
  end else begin
    FULF.SaveToFile(AFileName);
  end;
end;

procedure TULObjUsr.Browse;
begin
  Obj.Browse;
end;

function TULObjUsr.Edit: integer;
begin
  Result := Obj.Edit;
end;

function TULObjUsr.EditModal: integer;
begin
  Result := Obj.EditModal;
end;

function TULObjUsr.GetFileName: string;
begin
  if FULF <> nil then
    Result := FULF.FileName
  else if Obj <> nil then
    Result := Obj.FileName
  else
    Result := '';
end;
{/v0.23} {ulobju}

{v0.24}
{procedure TULObjUsr.SetObj(AObj: TULObj);
begin
  inherited;
  if Obj <> nil then
    ClassFieldsUpdate;
end;                  }

procedure TULObjUsr.SetFileName(const AFileName: string);
begin
  {v0.41}
  if FULF <> nil then
    FULF.FileName := AFileName;
  if Obj <> nil then
    Obj.FileName := AFileName;
  {/v0.41
  if FULF <> nil then
    FULF.FileName := AFileName
  else if Obj <> nil then
    Obj.FileName := AFileName;}
end;

procedure TULObjUsr.FileNew;
begin
  if Obj <> nil then begin
    Obj.Clear;
    if FULF <> nil then begin
      FULF.FileName := 'NONAME' + ULRecDefFileExt(Obj.RecID);
    end;
    {v0.32}
    ClassFieldsUpdate;
    {/v0.32}
  end;
end;

{$IFNDEF CONSOLE}
function TULObjUsr.FileOpen: boolean;
      { Opens OpenDialog, gives a chance to select existing file with
        RecID corresponding extension, using Obj.Caption as file description,
        if returns mrOK, then the content of the file assigned to Obj (i.e.
        replaced its current content) using LoadFromFile method. }
{v0.37}
var
  fn: string;
  dd: string;
{/v0.37}
begin
  Result := false;
  if Obj = nil then
    exit;
  {v0.37}
  fn := FileName;
  dd := Obj.ObjDesc.DefDir;
  if dd = '' then begin
    dd := ExtractFileDir(fn);
  end else begin
  end;
  if FileNameOpenSelect(fn, Obj.ObjDesc.Caption, Obj.ObjDesc.OpenFilter, dd) then begin
    LoadFromFile(fn);
    {v0.38}
    Result := true;
    {/v0.38}
  end;
  {/v0.37
  if FOpenDialog = nil then begin
    FOpenDialog := TOpenDialog.Create(Application);
  end;
  FOpenDialog.Filter := Obj.ObjDesc.Caption + '(*' + ULRecDefFileExt(Obj.RecID) +
     ')|*' + ULRecDefFileExt(Obj.RecID);
  if Obj.ObjDesc.DefDir <> '' then begin
    FOpenDialog.FileName := '';
    FOpenDialog.InitialDir := Obj.ObjDesc.DefDir;
  end else begin
    FOpenDialog.FileName := FileName;
  end;

  if FOpenDialog.Execute then begin
    LoadFromFile(FOpenDialog.FileName);
    Result := true;
  end;
  }
end;

function TULObjUsr.FileSaveAs: boolean;
var
  {v0.37}
  fn: string;
  od: TULObjDesc;
  {/v0.37
  d: string;}
begin{ulobju ulmtype}
  {v0.37}
  fn := FileName;
  od := Obj.ObjDesc;
  if FileNameSaveSelect(fn, od.Caption, od.SaveFilter, od.DefDir) then begin
    SaveToFile(fn);
    Result := true;
  end else begin
    Result := false;
  end;
  {/v0.37
  Result := false;
  if Obj = nil then
    exit;
  if FSaveDialog = nil then begin
    FSaveDialog := TSaveDialog.Create(Application);
  end;
  FSaveDialog.Filter := Obj.ObjDesc.Caption + '(*' + ULRecDefFileExt(Obj.RecID) +
      ')|*' + ULRecDefFileExt(Obj.RecID);
  d := ExtractFilePath(FileName);
  if d = '' then
    d := Obj.ObjDesc.DefDir;
  FSaveDialog.InitialDir := d;

  if FSaveDialog.Execute then begin
    SaveToFile(FSaveDialog.FileName);
    Result := true;
  end;
}
end;
{$ENDIF}
procedure TULObjUsr.UpdateDefDirs;
begin
end;
       {ulobjdes}
{/v0.24}

{v0.36}
{$IFNDEF CONSOLE}
procedure TULObjUsr.ULGridCreated(AGrid: TULStringGrid);
begin
{  FCurULGrid := AGrid;}              {aapgobju}
end;
{$ENDIF}
{/v0.36}

{v0.41}
function TULObjUsr.GetRelFileName: string;
begin
  Result := RelativeFileName(Obj.ObjDesc.DefDir, FileName, Obj.ObjDesc.DefDir);
end;

function TULObjUsr.GetFullFileName: string;
begin
  Result := AbsoluteFileName(Obj.ObjDesc.DefDir, FileName, Obj.ObjDesc.DefExt);
end;
{ulobjdes}
procedure TULObjUsr.SetFullFileName(const AFileName: string);
begin
  FileName := AbsoluteFileName(Obj.ObjDesc.DefDir, AFileName, Obj.ObjDesc.DefExt);
end;
{/v0.41}

{v0.44}         {ulobju}
procedure TULObjUsr.Clear;
begin
  Obj.Clear;
end;

procedure TULObjUsr.SaveIfNotVisible;
begin
  if Obj.Users.Count = 1 then
    Save;
end;

{$IFNDEF CONSOLE}
procedure TULObjUsr.ULGridFormActivated(AGrid: TULStringGrid);
begin
  {v0.46}
  FActiveGrid := AGrid;
  {/v0.46
  FGridActive := true;}
  {ulbrowsefrm}
end;

procedure TULObjUsr.ULGridFormDeactivated(AGrid: TULStringGrid);
begin
  {v0.46}
  FActiveGrid := nil;
  {/v0.46
  FGridActive := false;}
end;
{$ENDIF}
{/v0.44}

{v0.46}
{$IFNDEF CONSOLE}
function TULObjUsr.GetActiveGridForm: TForm;
begin
  Result := nil;
  if (FActiveGrid <> nil) and (FActiveGrid.Owner is TForm) then
    Result := TForm(FActiveGrid.Owner);
end;
{$ENDIF}
{/v0.46}

{v0.45}
procedure TULObjUsr.AfterBrowseChildInsert;
var
  c: TULObjUsr;
begin
  inherited;
  c := ActiveChild;
  if c <> nil then
    c.AfterBrowseInsert;
end;

procedure TULObjUsr.AfterBrowseInsert;
begin
end;

function TULObjUsr.GetActiveChild: TULObjUsr;
var
  o: TULObj;{ulantype}
  {v0.60}
  i: integer;
  {/v0.60}
begin
  Result := nil;
  o := Obj.ActiveChild;
  if o <> nil then begin
    {v0.51}
    if o.Users <> nil then
    {/v0.51}
    begin
      if (o.Users.Count > 0) then begin
        {v0.60 0.user can be ULStringGrid, so find first ULObjUsr:}
        for i := 0 to o.Users.Count - 1 do begin
          if TObject(o.Users.Items[i]) is TULObjUsr then begin
            Result := TULObjUsr(o.Users.Items[i]);
            exit;
          end;
        end;
        {/v0.60
        Result := TULObjUSr(o.Users.Items[0]);}
      end;
    end;
  end;
end;
{/v0.45}

{v0.47}
function TULObjUsr.GetCaption: string; {uldrtype}
  { returns string, that should appear in treeview for this ulobjusr node }
{v0.65}{/v0.65
var f:TULObjField;}
begin
  Result := '';
  if Obj <> nil then begin
    {v0.65}
    Result := Obj.Caption;
    {/v0.65
    f := Obj.EfNameField;
    if f <> nil then
      Result := f.AsUsrString;
    if Result = '' then
      Result := Obj.RecName;
    if Result = '' then
      Result := Obj.ObjDesc.Caption;
    if Result = '' then
      Result := Obj.ClassName;
    }
  end;
  if Result = '' then
    Result := ClassName;
end;

procedure TULObjUsr.Log(const msg: string);
begin
  ExeLog.Log(Caption + '.' + msg);
end;

procedure TULObjUsr.LogErr(const msg: string);
begin
  ExeLog.LogErr(Caption + '.' + msg);
end;

{/v0.47}

{v0.61}
function TULObjUsr.GetRecID: TULRecID;
begin
  Result := GetClassRecID;
end;
{/v0.61}

{v0.64}
procedure TULObjUsr.ObjAssign(Source: TULObj);
begin
  Obj.Assign(Source);
  ClassFieldsUpdate;
end;

procedure TULObjUsr.Assign(Source: TPersistent);
begin
  if Source is TULObjUsr then begin
    ObjAssign(TULObjUsr(Source).Obj);
  end else
    inherited Assign(Source);
end;
{/v0.64}

{v0.70}
{ override and return false if wanted some values ommited from combobox
  offering possible values for ULEnum field }
function TULObjUsr.AddingULEnumFieldValue(AULObjField: TULObjField): boolean;
begin
  Result := true;
end;
{/v0.70}
function CompareULObjUsr(UOU1, UOU2: pointer): integer;
var
  ow: TULObj;
  u1: TULObjUsr absolute uou1;
  u2: TULObjUsr absolute uou2;
  i1, i2: integer;
begin
  ow := u1.ULObjOwner.Obj;
  i1 := ow.ChildList.IndexOf(u1.Obj);
  i2 := ow.Childlist.IndexOf(u2.Obj);
  if i1 < i2 then
    Result := -1
  else if i1 > i2 then
    Result := 1
  else
    Result := 0;
end;

procedure TULObjUsr.Sort;
begin
  ChildList.Sort(CompareULObjUsr);
end;
{/TULObjUsr.}

{TULObjUsrsList}
constructor TULObjUsrsList.Create(AObjUsr: TULObjUsr);
begin
  inherited Create;
  FObjUsr := AObjUsr;
  {UpdateChilds;}
end;

{procedure TULObjUsrsList.UpdateChilds;
begin

end;}

destructor TULObjUsrsList.Destroy;
{var i: integer;}
begin
  while Count > 0 do
    TULObjUsr(Items[0]).Free;
  inherited Destroy;
end;
{/TULObjUsrsList}

{v0.41}
function ULObjUsrBrowserOpen(AULObjUsrClass: TULObjUsrClass; ARecID: TULRecID;
  const AFileName: TFileName; AMode: TOpenMode): TULObjUsr;
var
  s, t: TULObjUsr;
  fn: string;
  ext: string;
  sf: TULObjUsrClass;
begin
{  Result := nil;}
  sf := AULObjUsrClass;
  if ChangeFileExt(ExtractFileName(AFileName), '') = 'Default' then begin
    fn := ''
  end else
    fn := AFileName;
  ext := ExtractFileExt(fn);
  if ext = '' then
    ext := ULRecDefFileExt(ARecID);
  if (AMode = omCreate) then begin
    {fn is template file name}
    if (fn <> '') then begin
      fn := ChangeFileExt(fn, ext);
      if FileExists(fn) then begin
        t := sf.Load(fn);
        try
          s := sf.Load('');
          try
            s.Obj.Assign(t.Obj);
          except
            s.Free;
            raise;
          end;
        finally
          t.Free;
        end;
      end else begin
        raise Exception.Create(GetTxt({#}'Template file not found') + ' ' + fn);
      end;
    end else begin
      s := sf.Load('');
    end;
  end else begin
    s := sf.Load(fn);
  end;
  s.Obj.BrowserAutoClose := true;
  s.Obj.SetFlag(rfBrowseModal, false);
  s.Browse;
  Result := s;
end;
{/v0.41}

{v0.61}
{ Makes sure that instance of TULObjUsr descendant (class AULObjUsrClass)
  the AULObjUsr is initialized:
  - if AULObjUsr is nil, creates the instance by loading it from the AFileName
    if the file exists, if it does not, just creates the object and sets
    its filename to AFileName
  - if it is non nil, just returns true. }

function ULObjUsrGet(AULObjUsrClass: TULObjUsrClass; const AFileName: string;
  var AULObjUsr: TULObjUsr): boolean;
begin
  if AULObjUsr = nil then begin
    if FileExists(AFileName) then begin
      AULObjUsr := AULObjUsrClass.Load(AFileName);
    end else begin
      AULObjUsr := AULObjUsrClass.Load('');
      AULObjUsr.FileName := AFileName;
    end;
    { caller then can set some class dependent options, like:

      AULObjUsr.Obj.ObjDesc.GridOptions := [ogAutosizeBrowseCols];
      AULObjUsr.Obj.ObjDesc.ExcludedMenuItems := [omFocused, omPaste];
      AULObjUsr.Obj.ChildsSetFlag(rfOwnMessageOnSelect, true);
    }
  end;
  Result := true;
end;
{/v0.61}

{v0.24}
initialization
  RegisterClasses([TULObjUsr]);
finalization
  {FOpenDialog.Free;
  FSaveDialog.Free;}
{/v0.24}
end.
