unit ULStringGrid;{v0.24}
{ XStringGrid descendant aware of ULObj objects }
{
  (C) 2000 - 2002 Jindrich Jindrich, Pavel Pisa, PiKRON Ltd.

  Originators of the CHROMuLAN project:

  Jindrich Jindrich - http://www.jindrich.com
                      http://orgchem.natur.cuni.cz/Chromulan/
                      software developer, project coordinator
  Pavel Pisa        - http://cmp.felk.cvut.cz/~pisa
                      embeded software developer
  PiKRON Ltd.       - http://www.pikron.com
                      project initiator, sponsor, instrument developer

  The CHROMuLAN project is distributed under the GNU General Public Licence.
  See file COPYING for details.

  Originators reserve the right to use and publish sources
  under different conditions too. If third party contributors
  do not accept this condition, they can delete this statement
  and only GNU license will apply.
}

interface
{ $DEFINE DEBLOGGRID}{ulbrowsefrm}
{v0.58}
{$DEFINE V058}
{$DEFINE V059PI}{xstringgrid}
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Buttons, Grids, ExtCtrls, Menus, Clipbrd, ActnList,
  UtlType, WinUtl, PropUtl, Msgu, ExeLogu,
  ULRecTyp, ULRecUtl, ULObju, ULObjDes,
  XStringGrid, XStringGridRegister,
  XSAbout, CECheckList, CEForm, CEFormRegister, CEButton, LogFrm
  {$IFDEF DEBLOGGRID}
  //,DebugFrm ulobjctrl
  {$ENDIF}
  {v0.28},Language{/v0.28}
  ;

type
  EULStringGrid = class(Exception);

  TCellEditorItem = class(TObject)
  private
    FCellEditor: TCellEditor; { pointer to celleditor owned by window }
    FRecID: TULRecID; { for which RecID was the cell editor created }
    FULIndex: integer; { for which field the RecID object was the celleditor created }
  public
    constructor Create(AForm: TForm; AObj: TULObj; AFieldIndex: integer);reintroduce;
    property CellEditor: TCellEditor read FCellEditor;
    property RecID: TULRecID read FRecID;
    property ULIndex: integer read FULIndex;
  end;

  TCellEditors = class(TList)
    { maintainer of CellEditorItems for the window }
  private
    FForm: TForm;
  protected
    function Find(ARecID:TULRecID; AFieldIndex: integer; var ACellEditor: TCellEditor): boolean;
    function AddCellEditor(AObj:TULObj; AFieldIndex:integer): TCellEditor;
  public
    constructor Create(AForm: TForm);
    function Get(AObj: TULObj; AFieldIndex: integer): TCellEditor;
  end; {ulanrecs.lst}

  TULStringGrid = class;

  TLastEdit = class(TObject)
  private
    FGrid: TULStringGrid;
    FRow, FCol: integer;
    FField: TULObjField;
    FChild: TULObj;
    FValue: string;
  public
    constructor Create(AGrid: TULStringGrid);reintroduce;
    function Started: boolean;
    procedure Clear;
    procedure Start(ACol, ARow: integer; const AValue: string);
    procedure Stop(ACol, ARow: integer; const AValue: string);
    property Row: integer read FRow;
    property Col: integer read FCol;
    property Child: TULObj read FChild;
  end;

  TBrowseChilds = class(TList)
    {holds just pointers to ULObj, does not own them, index here corresponds
     to Row index (for FixedRows nil child assigned) }
  protected
    function GetChild(Index: integer):TULObj;
  public
    property Childs[Index: integer]: TULObj read GetChild; default;
  end;

  TULStringGrid = class(TXStringGrid){tstringgrid xstringgrid}
  private
    PopupMenu: TPopupMenu;
    LocalMenu: TPopupMenu;
    { Private declarations }
    FObj: TULObj;
    FCurChild: TULObj;
    {FNewChild: TULObj;}
      { non nil if new child was created but not saved or canceled yet }
    LCol, LRow: integer;
    LRecID: TULRecID;
      { what was the last active record's id (for redrawing captions) }
    IsInHandleCommand:boolean;
    LastEdit: TLastEdit;

    FSavingText:  boolean;
    {v0.50}
    FPostUpdateNeeded: boolean;
    {/v0.50}
    FAddingChild: boolean;
    FUpdatingGrid: boolean;

    {FEditingNewChild: boolean;}
      { true since NewRec called until saved or canceled }
    FInsertIndex: integer;
      { set to >= 0 (ActiveChildIndex) either if "Insert" from local menu
        was invoked or Shift key was pressed when Insert key was pressed.
        Otherwise -1 }

    FModified: boolean;

    {FEditEnabled: boolean;}
    FInternalValuesEnabled: boolean;
    FAllDetails: boolean;
    FAutoSizeBrowseCols: boolean;
    FAutoSizeForm: boolean;

    FMaxColWidths: TULBrowseColWidths;
    FMaxColCaptionWidths: TULBrowseColWidths;
    FFixedRows: integer;
    FFixedCols: integer;
    {FInternalValuesEnabled: boolean;}
    FBrowseChilds: TBrowseChilds;
      { list of childs of Obj selected for showing in browser }
    FBrowseMode: TULBrowseMode;
    FCellEditors: TCellEditors;
    FUpdateLockCount: integer;
    FInfo: longint;
    FUsingColors: boolean;
    FMoreChildRecIDs: boolean;
      { are in the browser rendered childs with different RecIDs? }
    FCurChildColCount: integer;
      { for what colcount was last time updated FCurChild }
    {v0.44}
    {FIsInSelectCell: boolean;}
    FColumnSortEnabled: boolean;
    FIsInSetObj: boolean;
    FFocusSelectedEnabled: boolean;
    {/v0.44}
    {v0.47}
    FEnterCount: integer;
    {/v0.47}
    {v0.50}
    FCloseFormOnObjDestroy: boolean;
    {/v0.50}
    FLastPostedChar: integer;
  protected
    procedure WMCommand(var Msg:TWMCommand);message WM_COMMAND;
    procedure MenuItemClick(Sender: TObject);
    {v0.24}
    procedure WMCUT(var Msg: TMessage); message WM_CUT;
    procedure WMCOPY(var Msg: TMessage); message WM_COPY;
    procedure WMPASTE(var Msg: TMessage); message WM_PASTE;
    {/v0.24}
    {procedure SaveText(AChild: TULObj; ACol,ARow:integer; const Value:string);}
    {procedure CheckSaveText(ACol, ARow:integer; const Value: string);}
    procedure SetBrowseMode(bm:TULBrowseMode);
    {procedure GridSelToObj{UpdateSelection}{called (e.g. before copy to clipboard) to
      reflect grid's selected region to FObj.Childs }
    function GetCurChild: TULObj;
    procedure SetCurChild(AObj:TULObj);
    procedure SetObj(AObj: TULObj);
    procedure UpdateBrowseMode;
    function GetBrowseChilds: TBrowseChilds;
    function GetCellEditors: TCellEditors;

    procedure GetDrawState(ACol, ARow: Longint;
      var AState: TGridDrawState);override;
    procedure GetCellColor(ACol, ARow: Longint;
      AState: TGridDrawState; var ATextColor: TColor; var ABackColor: TColor);override;
    procedure SetSelected(ACol, ARow: Longint; IsSelected: boolean);override;
    procedure SetSelectedRows(ACol, ARow1, ARow2: integer; IsSelected: boolean);override;
    function SelectCell(ACol, ARow: Integer):boolean;override;

    procedure SetEditEnabled(OnOff: boolean);
    function GetEditEnabled: boolean;

    procedure SetAlwaysShowEditor(OnOff: boolean);
    function GetAlwaysShowEditor: boolean;

    procedure SetInternalValuesEnabled(OnOff: boolean);
    procedure SetAllDetails(OnOff: boolean);
    procedure SetModified(OnOff: boolean);
    procedure SetAutoSizeBrowseCols(OnOff: boolean);
    procedure SetAutoSizeForm(OnOff: boolean);

    procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); override;
    procedure KeyDown(var Key: Word; Shift: TShiftState); override;
    procedure KeyUp(var Key: Word; Shift: TShiftState); override;
    procedure DoEnter; override;
    procedure DoExit; override;
    procedure WndProc(var Message: TMessage); override;
    function CellToField(ACol, ARow: integer; var AField: TULObjField): boolean;

    function HandleCommand(cm: integer):boolean;

    function AddNewRec(i:integer): boolean;
    { Create new child record of RecID that has index in allowed child
      RecIDs array = i.  }
    procedure UpdateCurChildFromObj(CheckForPresence:boolean);
    procedure Clear;
      { called from UpdateGrid, set ColCount = 0, RowCount = FixedRows }
    procedure UpdateColHeaders(oi: TULObjDesc; var AColWidthChanged: boolean);
    procedure CheckAutoSizeForm;
    procedure UpdateActiveField(ACol: integer);
    procedure CaptionUpdate;
    procedure Loaded; override;
  public
    procedure WMAppMessage(var Msg: TMessage); message WM_APPMESSAGE;
    constructor Create(AOwner: TComponent);{v0.64}override;{/v0.64 reintroduce;}
    destructor Destroy; override;
    procedure SetResult(const msg: string);

    procedure UpdateLock;
    procedure UpdateUnlock;

    procedure EditRecBtnClick(Sender: TObject);
    procedure NewRecBtnClick(Sender: TObject);
    procedure a1Click(Sender: TObject);
    procedure DelRecBtnClick(Sender: TObject);

    {procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormDestroy(Sender: TObject);}
    procedure GridGetEditText(Sender: TObject; ACol, ARow: Integer;
      var Value: String);
    procedure GridSetEditText(Sender: TObject; ACol, ARow: Integer;
      const Value: String); {onselectsell}
    procedure AllDetailsCheckBoxClick(Sender: TObject);
    procedure InternalValuesCheckBoxClick(Sender: TObject);

    { Public declarations }
    procedure UpdateGrid;

    function NewRec(AInsert:boolean; AChildRecIDIndex: integer): boolean;
    { Add new record. If AInsert is true, new rec. will be inserted before
      current child (if not sorted). If AChildRecIDIndex >= 0 then it specifies
      whis kind of child (of what RecID) should be created (index in ChildRecIDs
      array) }
    function EditRec: integer;
    procedure DelRec;

    property CurChild: TULObj read GetCurChild write SetCurChild;
    property Obj: TULObj read FObj write SetObj;
    property BrowseChilds: TBrowseChilds read GetBrowseChilds;
    property CellEditors: TCellEditors read GetCellEditors;
    property EditEnabled: boolean read GetEditEnabled write SetEditEnabled;
    property AlwaysShowEditor: boolean read GetAlwaysShowEditor write SetAlwaysShowEditor;
    property InternalValuesEnabled: boolean read FInternalValuesEnabled write
      SetInternalValuesEnabled;
    property AllDetails: boolean read FAllDetails write SetAllDetails;
    property Modified: boolean read FModified write SetModified;
    property AutoSizeBrowseCols: boolean read FAutoSizeBrowseCols write SetAutoSizeBrowseCols;
    property AutoSizeForm: boolean read FAutoSizeForm write SetAutoSizeForm;
    {v0.44}
    property ColumnSortEnabled: boolean read FColumnSortEnabled write FColumnSortEnabled;
    property FocusSelectedEnabled: boolean read FFocusSelectedEnabled write FFocusSelectedEnabled;
    {/v0.44}
    {v0.50}
    property CloseFormOnObjDestroy: boolean read FCloseFormOnObjDestroy write FCloseFormOnObjDestroy;
    {/v0.50}
  end;

const
  ULBrowDebLog: boolean = false;
{v0.36}
var
  CurULStringGrid: TULStringGrid = nil;
{/v0.36}

procedure Register;

implementation

{TBrowseChilds}
function TBrowseChilds.GetChild(Index: integer):TULObj;
begin
  if Index >= Count then
    Result := nil
  else
    Result := TULObj(Items[Index]);
end;
{/TBrowseChilds}

{TCellEditorItem}
constructor TCellEditorItem.Create(AForm: TForm; AObj: TULObj; AFieldIndex: integer);
begin
  inherited Create;
  if AForm = nil then
    raise Exception.Create('TCellEditorItem.Create AForm nil');
  if AObj = nil then
    raise Exception.Create('TCellEditorItem.Create AObj nil');
  FCellEditor := AObj.Fields[AFieldIndex].FldDesc.CreateCellEditor(AForm, AObj);
  FRecID := AObj.RecID;
  FULIndex := AFieldIndex;
end;
{/TCellEditorItem}

{TCellEditors}
constructor TCellEditors.Create(AForm: TForm);
begin
  inherited Create;
  if AForm = nil then
    raise Exception.Create('TCellEditors.Create AForm nil');
  FForm := AForm;
end;

function TCellEditors.Find(ARecID:TULRecID; AFieldIndex: integer; var ACellEditor: TCellEditor): boolean;
var
  ci: TCellEditorItem;
  i: integer;
begin
  Result := false;
  for i := 0 to Count - 1 do begin
    ci := TCellEditorItem(Items[i]);
    if (ci.RecID = ARecID) and (ci.ULIndex = AFieldIndex) then begin
      ACellEditor := ci.CellEditor;
      Result := true;
      exit;
    end;
  end;
end;

function TCellEditors.AddCellEditor(AObj:TULObj; AFieldIndex:integer): TCellEditor;
var
  ci: TCellEditorItem;
begin
  ci := TCellEditorItem.Create(FForm, AObj, AFieldIndex);
  try
    Add(ci);
  except
    ci.Free;
  end;
  Result := ci.CellEditor;
end;

function TCellEditors.Get(AObj: TULObj; AFieldIndex: integer): TCellEditor;
var ce: TCellEditor;
begin
  ce := nil;
  if not Find(AObj.RecID, AFIeldIndex, ce) then
    ce := AddCellEditor(AObj, AFieldIndex);
  Result := ce;
end;

{/TCellEditors}

{TULStringGrid}
constructor TULStringGrid.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  {Options := [goDrawFocusSelected, goFixedVertLine, goFixedHorzLine, goVertLine,
    goHorzLine, goRowSelect];}
  Options := [goFixedVertLine, goFixedHorzLine, goVertLine, goHorzLine
    {, goRangeSelect, goDrawFocusSelected, goRowSizing}
    ,goColSizing
    {,goRowMoving, goColMoving, goEditing, goTabs, goRowSelect,
    goAlwaysShowEditor, goThumbTracking}];
  FInsertIndex := -1;

  {FAutoSizeForm := true;}
  {v0.44}
  FFocusSelectedEnabled := true;
  {/v0.44}
  LastEdit := TLastEdit.Create(Self);
  MultiSelect := true;
  FFixedRows := FixedRows;
  FFixedCols := FixedCols;

  if csDesigning in ComponentState then
    exit;
  {v0.47}{/v0.47
  if CurULObj = nil then
    raise EEmptyCurULObj.Create('TULStringGrid.Create');}
  Obj := CurULObj;{cm_parentbidimodechanged messages windows}
  LocalMenu := TPopupMenu.Create(Self{AOwner});
  PopupMenu := TPopupMenu.Create(Self{AOwner});
  OnGetEditText := GridGetEditText;
  OnSetEditText := GridSetEditText;
  {OnSelectCell := GridSelectCell;}
  {v0.36}
  CurULStringGrid := Self;
  {v0.47}
  if Obj <> nil then
  {/v0.47}
    Obj.UsersNotify(cmULObjULGridCreated);
  {/v0.36}
end;

procedure TULStringGrid.GetDrawState(ACol, ARow: Longint;
  var AState: TGridDrawState);
var
  o: TULObj;
begin
  if FObj = nil then begin
    inherited;
    exit;
  end;
  o := BrowseChilds[ARow];
  if (o <> nil) and (o.IsFlagSet(rfSelected)) then
    AState := AState + [gdSelected]
  else
    AState := AState - [gdSelected];
end;

function TULStringGrid.CellToField(ACol, ARow: integer; var AField: TULObjField): boolean;
var
  o: TULObj;
  od: TULObjDesc;
  i: integer;
begin
  Result := false;
  o := BrowseChilds[ARow];
  if o <> nil then begin
    od := o.ObjDesc;
    i := ACol - FFixedCols;
    if (i < 0) or (i >= od.BrowseFieldCount) then
      exit;
    AField := o.Fields[od.BrowseFields[i].ULIndex];
    Result := true;
  end;
end;

procedure TULStringGrid.GetCellColor(ACol, ARow: Longint; AState: TGridDrawState;
  var ATextColor: TColor; var ABackColor: TColor);
var f:TULObjField;
begin
  inherited;
  if FUsingColors then begin
    if CellToField(ACol, ARow, f) then
      f.DoGetColor(AState, ATextColor, ABackColor); {getcolor ulobju}
  end;
end;

procedure TULStringGrid.SetSelected(ACol, ARow: Longint; IsSelected: boolean);
var
  o: TULObj;
begin
  if FObj = nil then begin
    inherited;
    exit;
  end;
  o := BrowseChilds[ARow];
  if o <> nil then begin
    if o.IsFlagSet(rfSelected) <> IsSelected then
      o.SetFlag(rfSelected, IsSelected);
  end;
  {v0.30}
  inherited;
  {/v0.30}
end;

procedure TULStringGrid.SetSelectedRows(ACol, ARow1, ARow2: integer; IsSelected: boolean);
begin
  if FObj <> nil then
    FObj.DoChangeLock;
  try
    inherited;
    {v0.44}
    if FocusSelectedEnabled then begin
      if (ARow1 = ARow2) and IsSelected then
        Row := ARow1;
    end;
    {/v0.44}
  finally
    if FObj <> nil then
      FObj.DoChangeUnlock;
  end;
  {CurChild := BrowseChilds[Row];}
end;

function TULStringGrid.GetCellEditors: TCellEditors;
begin
  if FCellEditors = nil then begin
    if Owner is TForm then
      FCellEditors := TCellEditors.Create(TForm(Owner));
  end;
  Result := FCellEditors;
end;

function TULStringGrid.GetBrowseChilds: TBrowseChilds;
begin
  if FBrowseChilds = nil then
    FBrowseChilds := TBrowseChilds.Create;
  Result := FBrowseChilds;
end;

{v0.38}
procedure TULStringGrid.CaptionUpdate;
begin
  if FObj <> nil then begin
    if Parent is TForm then
      TForm(Parent).Caption := {v0.41}FObj.GetWindowCaption(TForm(Parent)){/v0.41 FObj.WindowCaption};
  end;
end;
{/v0.38}

procedure TULStringGrid.SetObj(AObj: TULObj);
var
{  s: string;}
  i: integer;
{  o: TULObj;}
  w: integer;
begin
  CurULObj := nil;
  if FObj = AObj then
    exit;
  UpdateLock;
  {v0.44}
  FIsInSetObj := true;
  {/v0.44}
  try
    if FObj <> nil then begin
      FObj.UserUnregister(Self);
      FObj.ActionsFree;
      LastEdit.Clear;
      FObj := nil;
      CurChild := nil;
      FUsingColors := false;
    end;
    if AObj = nil then begin
      {v0.55}
      Clear;
      {/v0.55}
      Modified := true;
      exit;
    end;
    FObj := AObj;
    FObj.UserRegister(Self);
    FUsingColors := FObj.IsFlagSet(rfUsingColors) or
      (FObj.ChildWithFlagCount(rfUsingColors) > 0);
    {s := FObj.ObjDesc.Caption;
    o := TULObj(FObj.Owner);
    while (o <> nil) and (o is TULObj) do begin
      if o.RecID = ULFID then
        s := o.RelFileName + ':' + s
      else
        s := o.ObjDesc.Caption + '\' + s;
      o := TULObj(o.Owner);
    end;}
    {v0.38}
    CaptionUpdate;
    {/v0.38
    if Parent is TForm then
      TForm(Parent).Caption := FObj.WindowCaption;}
    BrowseChilds.Clear;
    FModified := true;
    LRow := -1;
    LCol := -1;
    {FLastEditCol:= -1;
    FLastEditRow:= -1;
    FLastEditField := nil;}
    LRecID := 0;
    for i := low(FMaxColCaptionWidths) to high(FMaxColCaptionWidths) do begin
      if Parent <> nil then
        w := Canvas.TextWidth('MMMM')
      else
        w := 40;
      FMaxColCaptionWidths[i] := w;
    end;
    EditEnabled := true;

    if UserMode = umSysOp then begin
      InternalValuesEnabled := true;
      AllDetails := true;
    end else begin
      {v0.44}
      if ogEditDisabled in Obj.ObjDesc.GridOptions then
        EditEnabled := false;
      if ogAlwaysShowEditor in Obj.ObjDesc.GridOptions then
        AlwaysShowEditor := true;
      if ogInternalValuesEnabled in Obj.ObjDesc.GridOptions then
        InternalValuesEnabled := true;
      if ogAllDetails in Obj.ObjDesc.GridOptions then
        AllDetails := true;
      if ogAutoSizeBrowseCols in Obj.ObjDesc.GridOptions then
        AutoSizeBrowseCols := true;
      if ogColumnSortEnabled in Obj.ObjDesc.GridOptions then
        ColumnSortEnabled := true; {ulrectyp}
      if ogFocusSelectedEnabled in Obj.ObjDesc.GridOptions then
        FocusSelectedEnabled := true;
      {/v0.44
      InternalValuesEnabled := false;
      AllDetails := false;}
    end;
    UpdateBrowseMode;
    {v0.64}
    ScrollBars := ssBoth;
    {/v0.64}
  finally
    {v0.44}
    FIsInSetObj := false;
    {/v0.44}
    UpdateUnlock;
  end;
end;

procedure TULStringGrid.SetBrowseMode(bm:TULBrowseMode);
begin
  if FBrowseMode <> bm then begin
    FBrowseMode := bm;
    LRecID := 0;
    Modified := true;
  end;
end;

procedure TULStringGrid.UpdateCurChildFromObj(CheckForPresence:boolean);
var
  Index: integer;
  i: integer;
begin
  if FObj = nil then begin
    {v0.58}
    {$IFDEF V058}
    CurChild := nil;
    {$ELSE}
    FCurChild := nil;
    {$ENDIF}
    {/v0.58
    FCurChild := nil}
  end else begin
    if CheckForPresence then begin
      {v0.58}
      {$IFDEF V058}
      CurChild := nil;
      {$ENDIF}
      {/v0.58}
      Index := -1;
      if FObj.ChildWithFlagNext(rfIsBrowseChild, Index) then
      repeat
        if Index = FObj.ActiveChildIndex then begin
          CurChild := FObj.ActiveChild;
          break;
        end;
      until not FObj.ChildWithFlagNext(rfIsBrowseChild, Index);
    end else
      CurChild := FObj.ActiveChild;
  end;
  if CurChild <> nil then begin
    if BrowseChilds.Count > 0 then begin
      i := BrowseChilds.IndexOf(FCurChild);
      if i >= 0 then
        Row := i;
    end;
  end;
end;

procedure TULStringGrid.Clear;
begin
  CurChild := nil;
  ColCount := 0;
  RowCount := FixedRows;
end;

procedure TULStringGrid.UpdateColHeaders(oi: TULObjDesc; var AColWidthChanged: boolean);
var
  w: integer;
  curcol: integer;
  j: integer;
  {v0.44}
  pref: string;
  {/v0.44}
begin
  if oi <> nil then begin
    if oi.BrowseFieldCount + FFixedCols > ColCount then begin
      ColCount := oi.BrowseFieldCount + FFixedCols;
      FixedCols := FFixedCols;
    end;

    j := 0;
    while j < oi.BrowseFieldCount do begin
      curcol := j + FFixedCols;
      {v0.44}
      pref := '';
      if {v0.64}(Obj <> nil) and {/v0.64}(oi.BrowseFields[j].ULIndex = Obj.SortChildFieldIndex) then
        pref := '[v]';
      {/v0.44}
      if FInternalValuesEnabled then begin
        Cells[curcol, 0] := {v0.44}pref + {/v0.44}trim(oi.BrowseFields[j].Name);
      end else begin
        Cells[curcol, 0] := {v0.44}pref + {/v0.44}trim(oi.BrowseFields[j].Caption);
      end;
      if AutoSizeBrowseCols then begin
        w := Canvas.TextWidth(Cells[curcol, 0]+'  ');
        if w > FMaxColWidths[curcol] then begin
          FMaxColWidths[curcol] := w;
          FMaxColCaptionWidths[curcol] := w;
          AColWidthChanged := true;
        end;
      end;
      inc(j);
    end;
    j := oi.BrowseFieldCount + FFixedCols;
  end else begin
    j := FFixedCols;
  end;

  while j < ColCount do begin
    Cells[j, 0] := '';
    inc(j);
  end;
  CheckAutoSizeForm;
end;

procedure TULStringGrid.CheckAutoSizeForm;
var
  w, h, i: integer;
  wdif: integer;
  hdif: integer;
  vs,hs: integer;
begin
  if FAutoSizeForm then begin
    if Parent is TForm then
    begin
      w := 0;
      for i := 0 to ColCount - 1 do
        inc(w, ColWidths[i]);
      h := 0;
      for i := 0 to RowCount - 1 do
       inc(h, RowHeights[i]);
      {w := GridWidth;
      h := GridHeight;}
      vs := GetSystemMetrics(SM_CXVSCROLL);{TForm(Parent).VertScrollBar.Size;}
      hs := GetSystemMetrics(SM_CYHSCROLL);{TForm(Parent).HorzScrollBar.Size;}

      if w < (Parent.ClientWidth - vs) then begin
        Parent.ClientWidth := w + vs;
      end else if w > (Parent.ClientWidth - vs) then begin
        wdif := (Parent.Width - (Parent.ClientWidth - vs));
        if w > (Screen.Width - wdif) then begin
          w := Screen.Width - wdif;
        end else begin
          w := w + vs;
        end;
        Parent.ClientWidth := w;
      end;

      if h < (Parent.ClientHeight - hs) then
        Parent.ClientHeight := h + hs
      else if h > (Parent.ClientHeight - hs) then begin
        hdif := (Parent.Height - (Parent.ClientHeight - hs));
        if h > (Screen.Height - hdif) then begin
          h := Screen.Height - hdif;
        end else begin
          h := h + hs;
        end;
        Parent.ClientHeight := h;
      end;
      {ulbrowsefrm tstringgrid}
    end;
  end;
end;

procedure TULStringGrid.UpdateGrid;
var
  childCnt,        { = FObj.ChildCount }
  rowCnt: integer; { = Rows - FFixedCols }
  o: TULObj;
  oi: TULObjDesc;
  actChildInd,     { = FObj.ActiveChildIndex }
  j, fldInd: integer;

  curChildChecked: boolean;
    { checked FObj.ActiveChild for presence in rendered FObj.Childs? }
  curChildFound: boolean;
    { found FObj.ActiveChild in rendered FObj.Childs? }
  maxfldcnt: integer;

  curcol, currow, w: integer;
  widthChanged: boolean;

  Index: integer;

  visChangeCnt: integer;
    { number of types of visual changes of the grid made during the update
      ( change of number/size of columns/rows, cell content, ...) }
  cRecID: TULRecID;
  prevRecID: TULRecID;
  {v0.44}
  firstSelIndex: integer;
  {/v0.44}


  procedure ChildUpdateStart;
  var
    i: integer;
  begin
    BrowseChilds.Clear;
    if childCnt > 0 then begin
      for i := 0 to FFixedRows - 1 do begin
        BrowseChilds.Add(nil);
      end;
    end;
    if AutoSizeBrowseCols then begin
      {v0.44}
      for i := 0 to ColCount - 1 do
        FMaxColWidths[i] := ColWidths[i];
      {/v0.44
      FMaxColWidths := FMaxColCaptionWidths;}
    end;
    LCol := -1;
    { ... force updating of header }
    if rowCnt <> childCnt then begin
      { adjust number of grid rows to FObj.ChildCount }
      inc(visChangeCnt);
      if childCnt = 0 then begin
        Clear;
      end else begin
        RowCount := childCnt + FFixedRows;
        FixedRows := FFixedRows;
      end;
    end;
    if childCnt > 0 then begin
      curChildChecked := true;
      prevRecID := 0;
      FMoreChildRecIDs := false;
      currow := FFixedRows;
      Index := -1;
    end;
  end;

  procedure ChildUpdateAdd;
  var
    j: integer;
    sel:boolean;
  begin
    o := FObj.Childs[Index];
    BrowseChilds.Add(o);
    oi := o.ObjDesc;
    oi.BrowseMode := FBrowseMode;
    if oi.BrowseFieldCount + FFixedCols > ColCount then begin
      { make sure that there is enough of columns for all childs: }
      ColCount := oi.BrowseFieldCount + FFixedCols;
      FixedCols := FFixedCols;
      inc(visChangeCnt);
    end else begin

    end;
    { find out what is the maximal number of childs' fields }
    if oi.BrowseFieldCount > maxfldcnt then begin
      maxfldcnt := oi.BrowseFieldCount;
      inc(visChangeCnt);
    end;

    cRecID := o.RecID;
    if (prevRecID <> 0) and (cRecID <> prevRecID) then
      FMoreChildRecIDs := true;
    prevRecId := cRecID;

    {currow := i + FFixedRows;}
    sel := o.IsFlagSet(rfSelected);
    {v0.47 no need to do this}{/v0.47
    SetSelectRow(currow, sel);}
    {v0.44}
    if sel and FocusSelectedEnabled then begin
      if firstSelIndex = -1 then
        firstSelIndex := Index;
      {v0.60}
      if actChildInd = -1 then begin
        actChildInd := Index;
        FObj.ActiveChild := o;
      end;
      {/0.60}
    end;
    {/v0.44}

    for j := 0 to oi.BrowseFieldCount - 1 do begin
      { scan all fields of current row child: }
      fldInd := oi.BrowseFields[j].ULIndex;
      curcol := j + FFixedCols;
      if (curcol <> LastEdit.Col) or (currow <> LastEdit.Row) or
         (LastEdit.Child <> o)
      then begin
        {.. do not disturb eventually changing cell value }
        if FInternalValuesEnabled then begin
          Cells[curcol, currow] := trim(o.Fields[fldInd].AsString);
        end else begin
          Cells[curcol, currow] := trim(o.Fields[fldInd].AsUsrString);
        end;
      end;

      if AutoSizeBrowseCols then begin
        if curcol < MaxULBrowseCols then begin
          w := Canvas.TextWidth(Cells[curcol, currow]+'  ');
          if w > FMaxColWidths[curcol] then begin
            FMaxColWidths[curcol] := w;
            widthChanged := true;
          end;
        end;
      end;
    end;

    if FFixedCols > 0 then begin
      if FInternalValuesEnabled then begin
        Cells[0, currow] := trim(o.RecIDStr);
      end else begin
        Cells[0, currow] := trim(oi.Caption);
      end;

      if AutoSizeBrowseCols then begin
        w := Canvas.TextWidth(Cells[0, currow]+'  ');
        if w > FMaxColWidths[0{curcol}] then begin
          FMaxColWidths[0{curcol}] := w;
          FMaxColCaptionWidths[0] := w;
          widthChanged := true;
        end;
      end;
    end;
    if Index = actChildInd then begin
      curChildFound := true;
    end;
    inc(currow);
  end;

  procedure ChildUpdateStop;
  begin
    if AutoSizeBrowseCols and (RowCount > 0) then begin
      if (maxfldcnt + FFixedCols) <> ColCount then begin
        if maxfldcnt = 0 then begin
          ColCount := 0;
        end else begin
          ColCount := maxfldcnt + FFixedCols;
          FixedCols := FFixedCols;
        end;
      end;
    end;
  end;

begin
  if FObj = nil then begin
    exit;
  end;
  if FUpdatingGrid then
    exit;

  {v0.44}
  {FObj.DoChangeLock;}
  {/v0.44}
  FUpdatingGrid := true;
  try

    if (FUpdateLockCount > 0) or (Parent = nil) then begin
      FModified := true;
      exit;
    end;
    {v0.44}
    {BeginUpdate;}
    try
      firstSelIndex := -1;
    {/v0.44}
      maxfldcnt := 0;
      widthChanged := false;
      visChangeCnt := 0;
      if FModified then begin
        inc(visChangeCnt);
        {v0.24}
        {v0.58}
        {$IFDEF V058}

        {v0.59pi}
        {/v0.59pi}
        {$IFNDEF V059PI}
        CurChild := nil;
        {$ENDIF}

        {$ELSE}
        FCurChild := nil;
        {$ENDIF}
        {/v0.58
        FCurChild := nil;}
        {/v0.24}
      end;

      childCnt := FObj.ChildWithFlagCount(rfIsBrowseChild);
      rowCnt := RowCount - FFixedRows;
      actChildInd := FObj.ActiveChildIndex;{Row - FFixedRows;}
      curChildFound := false;
      curChildChecked := false;
      oi := nil;

      if (rowCnt <> childCnt) or FModified then begin
        { FObj.ChildCount changed, or content of the grid modified }
        {$IFDEF DEBLOGGRID}
        {ExeLog.Log('before ChildsUpdateStart');}
        {$ENDIF}

        ChildUpdateStart;
        if childCnt > 0 then begin
          if FObj.ChildWithFlagNext(rfIsBrowseChild, Index) then
          begin
            { scan all child records: }            repeat
              ChildUpdateAdd;
            until not FObj.ChildWithFlagNext(rfIsBrowseChild, Index);

            if curChildFound then begin
              CurChild := FObj.ActiveChild;
            end else begin
              {v0.44}
              if FocusSelectedEnabled and (firstSelIndex >= 0) and FIsInSetObj then
                CurChild := FObj.Childs[firstSelIndex]
              else
              {/v0.44}
                CurChild := nil;
            end;
          end else begin
            { should not happen
            LRecID := 0;
            RowCount := FixedRows;}
          end;
        end;
        ChildUpdateStop;
        {$IFDEF DEBLOGGRID}
        {ExeLog.Log('after ChildsUpdateStop');}
        {$ENDIF}
      end;

      if (LCol <> Col) or (LRow <> Row) or FModified then
      begin
        { current cell (or its content) changed }

        if (childCnt > 0) and (actChildInd < childCnt) then begin
          UpdateCurChildFromObj(not curChildChecked);

          if CurChild <> nil then
            cRecID := CurChild.RecID
          else
            cRecID := 0;

          if FMoreChildRecIDs then begin
            { disable usage of ObjDesc of last child scanned for making Col headers
              if more childs with different RecID rendered }
            oi := nil;
          end;

          if (LRecID <> cRecID) or FModified then begin

            if CurChild <> nil then begin
              oi := CurChild.ObjDesc;
            end else begin
              { oi might be non nil, in the case that some other childs - all with
                the same RecID were found in the browser }
            end;

            UpdateColHeaders(oi, widthChanged);

            LRecID := cRecID;
          end;

        end else begin
          LRecID := 0;
          {Col := 0;
          Row := 0;}
        end;
        LCol := Col;
        LRow := Row;
        {Invalidate; tstringgrid}
      end;

      if AutoSizeBrowseCols and widthChanged then begin
        for j := 0 to ColCount - 1 do begin
          w := FMaxColWidths[j];
          if w <> 0 then
            ColWidths[j] := w;
        end;
        inc(visChangeCnt);
      end;
      {BrowseChilds[Row];}
      CheckAutoSizeForm;
      {v0.44}{/v0.44
      if visChangeCnt > 0 then
        Invalidate; }
      FModified := false;
    {v0.44}
    finally
      {EndUpdate;}
      {if visChangeCnt > 0 then
        Invalidate;}
    end;
    {/v0.44}
  finally
    {v0.44}
    {FObj.DoChangeUnlock;}
    {/v0.44}
    FUpdatingGrid := false;
    {v0.50}
    FPostUpdateNeeded := false;
    {/v0.50}
  end;
end;

procedure TULStringGrid.EditRecBtnClick(Sender: TObject);
begin
  EditRec;
end;

procedure TULStringGrid.NewRecBtnClick(Sender: TObject);
begin
  NewRec(false, -1);
end;

function TULStringGrid.NewRec(AInsert:boolean; AChildRecIDIndex: integer): boolean;
var
 i: integer;
 r: TULRecID;
 {v0.23}
 cnt:integer;
 {/v0.23}
 rd: PULRecDesc;
 mi: TMenuItem;
 cp,sp:TPoint;
begin
  Result := false;
  if FObj = nil then
    exit;
  if FObj.ObjDesc.ChildRecIDCount = 0 then
    exit;
  if AInsert then begin
    {v0.44}
    if omChildInsert in FObj.ObjDesc.ExcludedMenuItems then
      exit;
    {/v0.44}
    FInsertIndex := FObj.ActiveChildIndex;
  end else begin
    {v0.44}
    if omChildAdd in FObj.ObjDesc.ExcludedMenuItems then
      exit;
    {/v0.44}
    FInsertIndex := -1;
  end;

  if FObj.ObjDesc.ChildRecIDCount = 1 then begin
    AChildRecIDIndex := 0;
  end;

  if AChildRecIDIndex >= 0 then begin
    r := FObj.ObjDesc.ChildRecIDs[AChildRecIDIndex];
    if FObj.CanAddChildInBrowser(r, rd) then
      Result := AddNewRec(0);
  end else begin
    if PopupMenu = nil then
      exit;
    while PopupMenu.Items.Count > 0 do begin
      PopupMenu.Items[PopupMenu.Items.Count - 1].Free;
    end;
    cnt := 0;
    for i := 0 to FObj.ObjDesc.ChildRecIDCount - 1 do begin
      r := FObj.ObjDesc.ChildRecIDs[i];
      if FObj.CanAddChildInBrowser(r, rd) then begin
        mi := TMenuItem.Create(Self);
        if rd = nil then
          mi.Caption := ULRecIDToStrStrip(r)
        else
          mi.Caption := rd^.Caption;
        mi.OnClick := MenuItemClick;
        PopupMenu.Items.Add(mi);
        inc(cnt);
      end;
    end;
    if cnt = 0 then
      exit;
    if cnt = 1 then begin
      Result := AddNewRec(0);
      exit;
    end;
    cp.X := 0;
    cp.Y := 0;
    sp := {NewRecBtn.}ClientToScreen(cp);
    PopupMenu.Popup(sp.X, sp.Y);
  end;
end;

function TULStringGrid.HandleCommand(cm: integer):boolean;
var
  i: integer;
begin
  Result := false;
  if IsInHandleCommand then
    exit;
  try
    IsInHandleCommand := true;
    if PopupMenu <> nil then begin
      for i := 0 to PopupMenu.Items.Count - 1 do begin
        if cm = PopupMenu.Items[i].Command then begin
          AddNewRec(i);
          Result := true;
          break;
        end;
      end;
    end;
  finally
    IsInHandleCommand := false;
  end;
end;

procedure TULStringGrid.WMCommand(var Msg:TWMCommand);
begin
 if not HandleCommand(Msg.ItemID) then
   inherited;
end;

procedure TULStringGrid.WMAppMessage(var Msg: TMessage);
{v0.24}
var fmt:UINT;
    i:integer;
{/v0.24}
begin
  case Msg.wParam of

    cmULObjCanDestroy: begin
      if TULObj(Msg.lParam) = FObj then begin
        {v0.22 it can be destroyed, the browser will just be closed }
        {/v0.22
        Msg.Result := 1;}
      end;
    end;
    {v0.44}
    cmULObjUpdatedSelect: begin
      if (TULObj(Msg.lParam) = FObj) then begin
        if (not FSavingText) and (not FAddingChild) and (not FUpdatingGrid) then begin
          {v0.45}
          Invalidate;
          {/v0.45 Modified := true;}
        end;
      end;
    end;
    {/v0.44}

    cmULObjUpdated: begin
      if (TULObj(Msg.lParam) = FObj) then
      begin
        if ((not FSavingText) and (not FAddingChild) and (not FUpdatingGrid))
          {v0.50} or FPostUpdateNeeded{/v0.50}{ulrectyp}
        then
        begin
          Modified := true;
        end;
      end;
      {if (TULObj(Msg.LParam) = FNewChild) then
      begin
        if FNewChild <> nil then begin
          if not FNewChild.JustCreated then
            FNewChild := nil;
        end;
      end;}
    end;
    {v0.50}
    cmULObjPostUpdateNeeded: begin
      if (TULObj(Msg.lParam) = FObj) then
        FPostUpdateNeeded := true;
    end;
    {/v0.50}
    cmULObjBrowseFormBringToFront: begin {ulsqtype}
      if (TULObj(Msg.lParam) = FObj) then
      begin
        Msg.Result := 1;
        {v0.25}
        if Parent is TForm then
          Parent.BringToFront
        else
        {/v0.25}
          BringToFront;
      end;
    end;

    cmULObjAfterEdit: begin

    end;

    cmULObjDestroyed: begin
      {if TULObj(Msg.lParam) = CurChild then begin
        CurChild := nil;
        Modified := true;
      end; this message won't ever come}
      if TULObj(Msg.lParam) = FObj then begin
        {v0.59pi}
        {$IFDEF V059PI}
        if CellEditor <> nil then begin
          CellEditor.AbortEdit;
          CellEditor.Clear;
        end;
        for i := 0 to ColCount - 1 do begin
          Columns[i].Editor := nil;
        end;
        {$ENDIF}
        {/v0.59pi}
        Obj := nil;
        {v0.50}
        if CloseFormOnObjDestroy and (Owner is TForm) then with Owner as TForm do
          Release;
        {/v0.50}
      end;
      {v0.58}
      {v0.59pi IFDEF V058}
      if TULObj(Msg.lParam) = CurChild then begin
        {v0.59pi}
        {$IFDEF V059PI}
        if CellEditor <> nil then begin
          CellEditor.AbortEdit;
          CellEditor.Clear;
        end;
        for i := 0 to ColCount - 1 do begin
          Columns[i].Editor := nil;
        end;
        {$ENDIF}
        {/v0.59pi}
        CurChild := nil;
        Modified := true;
      end;
      {v0.59pi ENDIF}
      {/v0.58}
    end;


    {v0.24}
    cmQueryEditMenu: begin
      if FObj <> nil then begin
        Msg.Result := 0;
        if FObj.ChildWithFlagCount(rfSelected) > 0 then begin
          Msg.Result := Msg.Result or emCut or emCopy;
        end;
        fmt := RegisterClipboardFormat(ULObjFormat);
        if Clipboard.HasFormat(fmt) then
          Msg.Result := Msg.Result or emPaste;
      end else begin
        Msg.Result := 0;
      end
    end;
    {/v0.24
    cmClipCopy: begin
      if FObj <> nil then
        FObj.Actions.CopySelected(Self);
    end;

    cmClipPaste: begin
      if FObj <> nil then
        FObj.Actions.Paste(Self);
    end;

    cmClipCut: begin
      if FObj <> nil then begin
        Clipboard.Assign(FObj);
        FObj.ChildsDelete(0, rfSelected);
        UpdateGrid;
      end;
    end;
    }
    {v0.38}
    cmULObjCloseViews: begin
      if (TULObj(Msg.lParam) = FObj) then begin
        if Owner is TForm then begin
          TForm(Owner).Release;
        end;
      end;
    end;
    cmULObjFileNameChanged: begin
      if (TULObj(Msg.lParam) = FObj) then begin
        CaptionUpdate;
      end;
    end;
    {/v0.38}

    {v0.44}
    cmULObjChildsResorted: begin
      if (TULObj(Msg.lParam) = FObj) then begin
        FModified := true;
        UpdateGrid;
      end;
    end;

    cmULObjBeginUpdate: begin
      {if (TULObj(Msg.lParam) = FObj) then
        UpdateLock;}
    end;

    cmULObjEndUpdate: begin
      {if (TULObj(Msg.lParam) = FObj) then
        UpdateUnlock;}
    end;

    cmULObjActiveChildChanged: begin
      if (TULObj(Msg.lParam) = FObj) then begin
        {v0.47 }
        UpdateCurChildFromObj(false);
        {/v0.47
        FModified := true;
        UpdateGrid;}
      end;
    end;
    {/v0.44}
    cmULObjUsrActionNeeded: begin
      TAction(Msg.LParam).Execute;
    end;
  end;
end;

procedure TULStringGrid.WMCUT(var Msg: TMessage);
begin
  if FObj <> nil then begin
    Clipboard.Assign(FObj);
    FObj.ChildsDelete(0, rfSelected);
    {UpdateGrid;}
  end;
end;

procedure TULStringGrid.WMCOPY(var Msg: TMessage);
begin
  if FObj <> nil then begin
    FObj.Actions.CopySelected(Self);{ulfobju}
    {v0.31}
    {XStringGrid .CopySelectedToClipboard;}
    {/v0.31}
  end;
end;

procedure TULStringGrid.WMPASTE(var Msg: TMessage);
begin
  if FObj <> nil then
    FObj.Actions.Paste(Self);
end;

procedure TULStringGrid.a1Click(Sender: TObject);
begin
  HandleCommand(TMenuItem(Sender).Command);
end;

procedure TULStringGrid.MenuItemClick(Sender: TObject);
begin
  HandleCommand(TMenuItem(Sender).Command);
end;

function TULStringGrid.AddNewRec(i: integer): boolean;
var
  cnt, j: integer;
  r: TULRecID;
  rd: PULRecDesc;
begin
  Result := false;
  if FObj = nil then
    exit;
  cnt := 0;
  for j := 0 to FObj.ObjDesc.ChildRecIDCount - 1 do begin
    r := FObj.ObjDesc.ChildRecIDs[j];
    if FObj.CanAddChildInBrowser(r, rd) then begin
      if cnt = i then begin
        {v0.44}
        if FInsertIndex >= 0 then begin
          if FObj.ObjectUsersNotify(cmULObjBeforeBrowseChildInsert, FObj.ActiveChild) <> 0 then
            exit;
        end else begin
          if FObj.ObjectUsersNotify(cmULObjBeforeBrowseChildInsert, nil) <> 0 then
            exit;
        end;
        {/v0.792
        if FObj.UsersNotify(cmULObjBeforeBrowseChildInsert) <> 0 then
          exit;
        }

        FObj.DoChangeLock;
        {/v0.44}
        FAddingChild := true;
        try
          if FInsertIndex >= 0 then begin
            FObj.InsertNextChild := true;
            FInsertIndex := -1;
          end;
          CurChild := FObj.Add(r);
          CurChild.JustCreated := true;
          Result := true;
        finally
          FAddingChild := false;
          {v0.44}
          FObj.DoChangeUnlock;
          FObj.UsersNotify(cmULObjAfterBrowseChildInsert);
          {/v0.44}
        end;
        if not EditEnabled then begin
          { invoke form edit window if inplace editors disabled }
          if EditRec <> mrOK then;
          {DelRec;}
        end else begin
          Modified := true;
          if ColCount > FixedCols then
            Col := FixedCols;
          { select first cell of the current child (for eventual entering of value) }
        end;
        break;
      end;
      inc(cnt);
    end;
  end;
end;

function TULStringGrid.EditRec: integer;
begin
  Result := mrCancel;
  if CurChild <> nil then begin
    if CurChild.IsFlagSet({v0.23}rfIsBrowseChild{/v0.23 rfVisible}) and CurChild.IsFlagSet(rfEnabled) then
    begin
      if CurChild.IsFlagSet(rfBrowseOnEdit) then begin
        Result := CurChild.Browse;
      end else begin
        if (Owner is TForm) and (fsModal in TForm(Owner).FormState) then
          Result := CurChild.EditModal {ulobju}
        else
          Result := CurChild.Edit;
      end;
    end;
  end;
end;

procedure TULStringGrid.DelRecBtnClick(Sender: TObject);
begin
  DelRec;
end;

procedure TULStringGrid.DelRec;
begin
  if CurChild <> nil then begin
    if CurChild.IsFlagSet(rfCantDelete) or (not CurChild.CanDestroy) then begin
      if UserMode = umSysOp then begin
        if ShowMessage(GetTxt({#}'Really destroy object used by other objects (GPF can follow)?'), {v0.75}smNoYes{/v0.75 smYesNo}, 0) <> cmYes then
          exit;
      end else begin
        {v2.29}
          ShowMessage(GetTxt({#}'Can not delete object used by other objects.'), smError, 0);
        {/2.29}
        exit;
      end;
    end else begin
      if ShowMessage(GetTxt({#}'Really delete the current row?'), smNoYes, 0) <> cmYes then
        exit;
    end;
    {v0.44}
    {v0.46}
    {/v0.46
    if Obj.UsersNotify(cmULObjBeforeBrowseChildDelete) <> 0 then
      exit;}{ulrectyp}
    {/v0.44}
    {v0.46}        {ulobjact}
    CurChild.Delete;
    {/v0.46
    CurChild.Free;}
    if (Row >= FixedRows) and (Col >= FixedCols) then
      CurChild := BrowseChilds[Row];
    {v0.44}
    Obj.UsersNotify(cmULObjAfterBrowseChildDelete);
    {/v0.44}

    SendMessage(Application.MainForm.Handle, WM_APPMESSAGE, cmULObjAfterBrowseEdit,
      longint(FObj));
  end;
end;

procedure TULStringGrid.KeyDown(var Key: Word; Shift: TShiftState);
label
  ex;
begin
  case Key of
    vk_Insert: begin
      NewRec(ssShift in Shift, -1);
      {v0.64}Key := 0;{/v0.64}
    end;
    vk_Delete: begin
      DelRec;
      {v0.64}Key := 0;{/v0.64}
    end;
    {v0.24}{/v0.24
    vk_Return: EditRec;}
    {v0.61}
    vk_Return: begin
      if FBrowsingForChild then begin
        if Owner is TForm then begin
          TForm(Owner).ModalResult := mrOK;
          Key := 0;
        end;
      end;
    end;
    {/v0.61}
    vk_Space, ord('0')..ord('9'),ord('A')..ord('Z'), VK_NUMPAD0..VK_DIVIDE{, ord('a')..ord('z')}: begin
      ExeLog.Log(Format('KeyDown begin vk=%d shift=%d lastPosted=%d', [Key, 0{longint(Shift)}, FLastPostedChar]));
      if (CellEditor = nil) or (not CellEditor.Editing) then begin
        ExeLog.Log('not Editing');
        if FLastPostedChar <> Key{Message.WParam} then begin
          PostMessage(Handle, WM_KEYDOWN, Key{Message.wParam}, 0);
          FLastPostedChar := Key;//Message.wParam;
        end;
      end else begin
        ExeLog.Log('Editing');
        if FLastPostedChar <> 0 then begin
          if CellEditor is TMetaCellEditor then with TMetaCellEditor(CellEditor) do begin
            PostMessage(Editor.Handle, WM_KEYDOWN, Key, {Message.WParam} 0);
          end;
        end;
        FLastPostedChar := 0;
      end;
      ExeLog.Log(Format('KeyDown end key=%d shift=%d FLastPostedChar=%d', [Key, 0{longint(Shift)}, FLastPostedChar]));
      //ExeLog.Log(Format('KeyDown end w=%d l=%d FLastPostedChar', [Message.wParam, Message.lParam, FLastPostedChar]));
    end;

  {v0.64} {/v0.64 else
    inherited;}
  end;
  {v0.64}
  { 0.61 introduced bug - ignoring vk_return even if it should not }
  inherited;
  {/v0.64}
end;

{procedure TULStringGrid.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  Action := caFree;
end;}

destructor TULStringGrid.Destroy;
var
  i: integer;
  o: TULObj;
begin
  if FObj <> nil then begin
    {v0.31}
    o := FObj;
    {/v0.31}

    Obj := nil;
    {v0.31}
    o.UsersNotify(cmULObjULGridDestroyed);
    {/v0.31}
    {v0.47}
    if o.BrowserAutoClose then
      o.TopOwner.Free;
    {/v0.47}
  end;
  for i := 0 to ColCount - 1 do begin
    Columns[i].Editor := nil;
  end;
  FBrowseChilds.Free;
  FCellEditors.Free;
  LastEdit.Free;
  inherited Destroy;
end;

procedure TULStringGrid.GridGetEditText(Sender: TObject; ACol, ARow: Integer;
  var Value: String);
{var
  j: integer;
  n: shortstring;
  v: AnsiString;
  oi: TULObjDesc;}
begin
  {$IFDEF DEBUG}
  if ULBrowDebLog then begin
    ExeLog.Log('OnGetText ' + IntToStr(ACol) + ',' + IntToStr(ARow) + ' :' + Value + ':');
  end;
  {$ENDIF}
  LastEdit.Start(ACol, ARow, Value);
  {
  CheckSaveText(ACol, ARow, Value);
  FLastEditCol := ACol;
  FLastEditRow := ARow;
  FLastEditChild := CurChild;
  FLastEditValue := Value;}
end;

procedure TULStringGrid.GridSetEditText(Sender: TObject; ACol, ARow: Integer;
  const Value: String);
var c:TULObj;
begin
  if not LastEdit.Started then { unfortunately XStrinGrid can call this twice }
    exit;
  {$IFDEF DEBUG}
  if ULBrowDebLog then
    ExeLog.Log('OnSetText ' + IntToStr(ACol) + ',' + IntToStr(ARow) + ' :' + Value + ':');
  {$ENDIF}
  c := LastEdit.Child;
  {v0.47}
  FSavingText := true;
  try
  {/v0.47}
    LastEdit.Stop(ACol, ARow, Value);
  {v0.47}
  finally
    FSavingText := false;
  end;
  {/v0.47}
  SendMessage(Application.MainForm.Handle, WM_APPMESSAGE, cmULObjAfterBrowseEdit,
    longint(c));
  SendMessage(Application.MainForm.Handle, WM_APPMESSAGE, cmULObjAfterBrowseEdit,
    longint(FObj));
  {v0.75}
  c.UsersNotify(cmULObjAfterBrowseEdit);
  {/v0.75}
{  FLastEditValue := Value;
  FLastEditCol := ACol;
  FLastEditRow := ARow;
  CheckSaveText(ACol, ARow, Value);}
end;

{
procedure TULStringGrid.CheckSaveText(ACol, ARow:integer; const Value: string);
begin
  if (ACol <> FLastEditCol) or (ARow <> FLastEditRow) then begin
    if FLastEditRow >= 0 then
      SaveText(FLastEditChild, FLastEditCol, FLastEditRow, FLastEditValue);
  end else begin
    FLastEditValue := Value;
  end;
end;
}

{
procedure TULStringGrid.SaveText(AChild: TULObj; ACol,ARow:integer; const Value:string);
var
  j: integer;
begin
  if (AChild = nil) then
    exit;
  j := ACol - FFixedCols;
    FSavingText := true;
    try
      if j < AChild.ObjDesc.BrowseFieldCount then begin
        if FInternalValuesEnabled then begin
          AChild.Fields[AChild.ObjDesc.BrowseFields[j].ULIndex].AsString := Value;
        end else begin
          AChild.Fields[AChild.ObjDesc.BrowseFields[j].ULIndex].AsUsrString := Value;
        end;
        SendMessage(Application.MainForm.Handle, WM_APPMESSAGE, cmULObjAfterBrowseEdit,
          longint(AChild));
        SendMessage(Application.MainForm.Handle, WM_APPMESSAGE, cmULObjAfterBrowseEdit,
          longint(FObj));
      end;
    finally
      FSavingText := false;
    end;
  end;
  FLastEditRow := -1;
  FLastEditCol := -1;
  FLastEditValue := '';
  FLastEditChild := nil;
end;
}
function TULStringGrid.SelectCell(ACol, ARow: Integer): boolean;
begin
  Result := inherited SelectCell(ACol, ARow);
  if Result then begin
{$IFDEF DEBLOGGRID}
  DebLog('SelectCell(' + IntToStr(ACol) + ',' + IntToStr(ARow) + '), from Col=' + IntToStr(Col) +
   ' Row=' + IntToStr(Row));
{$ENDIF}
    if not FUpdatingGrid then begin
      CurChild := BrowseChilds[ARow];
      {v0.24}
      UpdateActiveField(ACol);
      {/v0.24}
    end;
    {v0.44}
    {if not FIsInSelectCell then begin
      FIsInSelectCell := true;
      try
        SetSelectedRows(ACol, ARow, ARow, true);
      finally
        FIsInSelectCell := false;
      end;
    end;}
    {/v0.44}

  end;
{  CheckSaveText(ACol, ARow, '');}
end;

procedure TULStringGrid.DoExit;
begin
  { CheckSaveText(-1, -1, ''); }
  inherited;
end;

procedure TULStringGrid.UpdateBrowseMode;
begin
{if AllDetailsCheckBox.Checked then}
  if AllDetails then
    SetBrowseMode(bmFull)
  else
    SetBrowseMode(bmShort);
end;

procedure TULStringGrid.AllDetailsCheckBoxClick(Sender: TObject);
begin
  UpdateBrowseMode;
{  FModified := true;
  if AllDetailsCheckBox.Checked then
    SetBrowseMode(bmFull)
  else
    SetBrowseMode(bmShort)}
end;

procedure TULStringGrid.InternalValuesCheckBoxClick(Sender: TObject);
begin
  FInternalValuesEnabled := not FInternalValuesEnabled;{InternalValuesCheckBox.Checked;}
  Modified := true;
end;

procedure TULStringGrid.WndProc(var Message: TMessage);
begin
  case Message.Msg of
    WM_LBUTTONDOWN: begin
      FInfo := 0;
    end;
    WM_HSCROLL: begin
      FInfo := 0;
    end;
    WM_VSCROLL: begin
      FInfo := 0;
    end;
    WM_SYSCOMMAND: begin
      FInfo := 0;
    end;
  end;{ messages }
  inherited;
end;

procedure TULStringGrid.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
var
  c, r: integer;
  {v0.44}
  f: TULObjField;
  {/v0.44}
begin
  inherited;
  if (Button = mbLeft) and (Shift - [ssLeft] = []) then begin
    MouseToCell(X, Y, c, r);
    {v0.44}
    if (r = 0) then begin
      if (c = 0) then begin
        ReleaseCapture;
        Perform(WM_SysCommand, SC_DragMove, 0);
      end else begin
        if ColumnSortEnabled then begin
          if CellToField(c, FixedRows, f) then begin
            Obj.SortByField(f.FldDesc.ULIndex);
          end;
        end;
      end
    end else begin
      {v0.44}
      if c <> 0 then begin
        DeSelectAll;
        MouseDownRow := -1;
        {xstringgrid}
        //PostMessage(Handle, WM_CHAR, 32, 0)
      end;
      {/v0.44}
    end;
    {/v0.44
    if (c = 0) and (r = 0) then begin
      ReleaseCapture;
      Perform(WM_SysCommand, SC_DragMove, 0);
    end;}
    if ((c = -1) and (r = - 1))
    then begin
      ReleaseCapture;
      Perform(WM_SysCommand, SC_SizeRightDown, 0);
    end; {tstringgrid}
  end;
  if CellEditor = nil then begin {xstringgrid}
  end;
end;

procedure TULStringGrid.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
var
  cp, sp: TPoint;
label ex;
begin
  if FObj = nil then
    goto ex;
  if Button = mbRight then begin
    if LocalMenu <> nil then begin
      cp.x := X;
      cp.y := Y;
      sp := ClientToScreen(cp);
      {v0.24}
      UpdateActiveField(Col);
      {/v0.24}
      FObj.Actions.LocalMenuBuild(Self, LocalMenu);{ulobju ulobjact}
      LocalMenu.Popup(sp.x, sp.y);
      exit;
    end;
  end;
ex:
  inherited;
end;

procedure TULStringGrid.KeyUp(var Key: Word; Shift: TShiftState);
begin
  inherited;
end;

function TULStringGrid.GetCurChild: TULObj;
begin
  Result := FCurChild;
{ Result := nil;
  if FObj <> nil then
    Result := FObj.ActiveChild; }
end;

{v0.24}
procedure TULStringGrid.UpdateActiveField(ACol: integer);
var
  i: integer;
  f: TULObjFldDesc;
begin
  if FObj = nil then
    exit;
  if CurChild = nil then
    exit;
  i := ACol - FixedCols;
  if (i < 0) or (i >= CurChild.ObjDesc.BrowseFieldCount) then begin
    FObj.ActiveChildFieldIndex := -1;
  end else begin
    f := CurChild.ObjDesc.BrowseFields[i];
    FObj.ActiveChildFieldIndex := f.ULIndex;
    Hint := f.Hint;
  end;
end;

procedure TULStringGrid.SetCurChild(AObj:TULObj);
var
  i, c: integer;
  f: TULObjFldDesc;
  prevRecID: TULRecID;
  cRecID: TULRecID;
  ch: boolean;
  oi: TULObjDesc;
  {v0.24}
  ce: TCellEditor;
  {/v0.24}
begin
  {v0.47}
  {$IFDEF DEBLOGGRID}
  {ExeLog.Log('SetCurChild begin');}
  {$ENDIF}
  try
  {/v0.47}
    if (AObj = FCurChild) and (FCurChildColCount = ColCount) then
      exit;

    {v0.58}
    {$IFDEF V058}
    if (FCurChild <> nil) then begin
      FCurChild.UserUnregister(Self);
    end;
    {$ENDIF}
    {/v0.58}

    FCurChildColCount := ColCount;
    prevRecID := 0;
    {cRecID := 0;}
    if FCurChild <> nil then
      prevRecID := FCurChild.RecID;
    FCurChild := AObj;

    {v0.58}
    {$IFDEF V058}
    if (FCurChild <> nil) then begin
      FCurChild.UserRegister(Self);
    end;
    {$ENDIF}
    {/v0.58}

    if FObj <> nil then begin
      {$IFDEF DEBLOGGRID}
      {ExeLog.Log('SetCurChild before FObj.ActiveChild=');}
      {$ENDIF}
      FObj.ActiveChild := AObj;{ulobju}
      {$IFDEF DEBLOGGRID}
      {ExeLog.Log('SetCurChild after FObj.ActiveChild=');}
      {$ENDIF}
    end;
    { list of childs of Obj selected for showing in browser }
    if AObj <> nil then begin
      cRecID := AObj.RecID;
      for i := 0 to AObj.ObjDesc.BrowseFieldCount - 1 do begin
        c := i + FixedCols;
        if c < ColCount then begin
          f := AObj.ObjDesc.BrowseFields[i];
          if ({v0.36}(not AObj.IsFlagSet(rfWriteLocked)) and {/v0.36} f.Enabled)
            or (UserMode = umSysOp) then
          begin
            ce := CellEditors.Get(AObj, f.ULIndex);
            if ce is TButtonCellEditor then begin
              {v0.44}
              TButtonCellEditor(ce).OnClick := Obj.Actions.ChildBtnEditClick;
              {/v0.44
              TButtonCellEditor(ce).OnClick := Obj.Actions.ChildFileSelect;}
              {UlObjAct}
            end;
            Columns[c].Editor := ce;
          end else begin
            Columns[c].Editor := nil;
          end;
        end;
      end;
      {v0.24}
      i := AObj.ObjDesc.BrowseFieldCount + FixedCols;
      for c := i to ColCount - 1 do begin
        Columns[c].Editor := nil;
      end;
      {/v0.24}
    end else begin
      cRecID := 0;
      for c := 0 to ColCount - 1 do
        Columns[c].Editor := nil;
    end;

    if cRecID <> prevRecID then begin
      if CurChild <> nil then
        oi := CurChild.ObjDesc
      else
        oi := nil;
      ch := false;
      {$IFDEF DEBLOGGRID}
      {ExeLog.Log('SetCurChild before UpdateColHeaders');}
      {$ENDIF}
      UpdateColHeaders(oi, ch);
      {$IFDEF DEBLOGGRID}
      {ExeLog.Log('SetCurChild after UpdateColHeaders');}
      {$ENDIF}
    end;
  {v0.47}
  finally
    {$IFDEF DEBLOGGRID}
    {ExeLog.Log('SetCurChild end');}
    {$ENDIF}
  end;
  {/v0.47}

end;

procedure TULStringGrid.DoEnter;
begin
  inherited;
  if (Parent is TForm) {v0.47} and (FObj <> nil){/v0.47} then{ulobju}
    TForm(Parent).Caption := {v0.41}FObj.GetWindowCaption(TForm(Parent)){/v0.41 FObj.WindowCaption};
  {v0.47}
  inc(FEnterCount);
  if FEnterCount = 1 then
  {/v0.47}
  Modified := true;
end;

procedure TULStringGrid.SetAlwaysShowEditor(OnOff: boolean);
begin
  if AlwaysShowEditor <> OnOff then begin
    if OnOff then begin
      Options := Options + [goAlwaysShowEditor];
    end else begin
      Options := Options - [goAlwaysShowEditor];
    end;
  end;
end;

function TULStringGrid.GetAlwaysShowEditor: boolean;
begin
  Result := goAlwaysShowEditor in Options;
end;

procedure TULStringGrid.SetEditEnabled(OnOff: boolean);
begin
  if EditEnabled <> OnOff then begin
    {FEditEnabled := OnOff;}
    if OnOff then
      Options := Options + [goEditing]
    else
      Options := Options - [goEditing];
    Modified := true;
  end;
end;

function TULStringGrid.GetEditEnabled: boolean;
begin
  Result := goEditing in Options;
end;

procedure TULStringGrid.SetInternalValuesEnabled(OnOff: boolean);
begin
  if FInternalValuesEnabled <> OnOff then begin
    FInternalValuesEnabled := OnOff;
    Modified := true;
  end;
end;

procedure TULStringGrid.SetAllDetails(OnOff: boolean);
begin
  if FAllDetails <> OnOff then begin
    FAllDetails := OnOff;
    UpdateBrowseMode;
  end;
end;

procedure TULStringGrid.SetModified(OnOff: boolean);
begin
  FModified := OnOff;
  if FModified then begin
    if not FUpdatingGrid then
      UpdateGrid;
  end;
end;

procedure TULStringGrid.UpdateLock;
begin
  inc(FUpdateLockCount);
end;

procedure TULStringGrid.UpdateUnlock;
begin
  dec(FUpdateLockCount);
  if FUpdateLockCount = 0 then begin
    if FModified then
      UpdateGrid;
  end;
end;

procedure TULStringGrid.SetAutoSizeBrowseCols(OnOff: boolean);
begin
  if FAutoSizeBrowseCols <> OnOff then begin
    FAutoSizeBrowseCols := OnOff;
    Modified := true;
  end;
end;

procedure TULStringGrid.SetAutoSizeForm(OnOff: boolean);
begin
  if FAutoSizeForm <> OnOff then begin
    FAutoSizeForm := OnOff;
    Modified := true;
  end;
end;

procedure TULStringGrid.SetResult(const msg: string);
begin
  raise EULStringGrid.Create(msg);
end;

{v0.44}
{procedure TULStringGrid.SetUpdateState(AUpdating: boolean);}
  {called from XStringGrid BeginUpdate/EndUpdate}
{begin
  inherited;
  if AUpdating then begin
  UpdateLock
  end else begin
    UpdateUnlock;
  end;
end;}
{/v0.44}

{v0.64}
procedure TULStringGrid.Loaded;
begin
  inherited;
  ScrollBars := ssBoth;
end;
{/v0.64}

{/TULStringGrid.} //TStringGrid

{TLastEdit}
constructor TLastEdit.Create(AGrid: TULStringGrid);
begin
  inherited Create;
  FGrid := AGrid;
  Clear;
end;

function TLastEdit.Started:boolean;
begin
  Result := FField <> nil;
end;

procedure TLastEdit.Clear;
begin
  FRow := -1;
  FCol := -1;
  FChild := nil;
  FValue := '';
  FField := nil;
end;

procedure TLastEdit.Start(ACol, ARow: integer; const AValue: string);
{v0.46}
var ce: TCellEditor;
{/v0.46}
begin
  if FGrid.CellToField(ACol, ARow, FField) then begin
    FChild := FField.ObjFields.Obj;
    {v0.46}
    ce := FGrid.CellEditors.Get(FChild, FField.FldDesc.ULIndex);
    if ce <> nil then
      FField.FldDesc.UpdateCellEditor(ce, FChild);
    {/v0.46}
    FCol := ACol;
    FRow := ARow;
    FValue := AValue;
  end;
end;

procedure TLastEdit.Stop(ACol, ARow: integer; const AValue: string);
begin
  if not Started then begin
    {FGrid.SetResult('SetEditText - GetEditText was not called.');}
  end else begin
    if (FCol <> ACol) or (ARow <> FRow) then begin
      FGrid.SetResult('SetEditText - dif row/col from GetEditText');
    end;
    if FGrid.InternalValuesEnabled then begin
      {v0.44}
      if FField.AsString <> AValue then begin
        FField.AsString := AValue;
        FField.FieldUsersNotify(cmULObjFieldChangedInBrowser);
      end;
      {/v0.44
      FField.AsString := AValue;}
    end else begin
      {v0.44}
      if FField.AsUsrString <> AValue then begin
        FField.AsUsrString := AValue;
        FField.FieldUsersNotify(cmULObjFieldChangedInBrowser);
      end;
      {/v0.44
      FField.AsUsrString := AValue;}
    end;
  end;{ulobju}
  Clear;
end;

{/TLastEdit}

procedure Register;
begin
  RegisterComponents('Chromulan', [TULStringGrid]);
end;
                        {ulobjctrl}
end.
