{
 ***************************************************************************
 *                                                                         *
 *   This source is free software; you can redistribute it and/or modify   *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This code is distributed in the hope that it will be useful, but      *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   A copy of the GNU General Public License is available on the World    *
 *   Wide Web at <http://www.gnu.org/copyleft/gpl.html>. You can also      *
 *   obtain it by writing to the Free Software Foundation,                 *
 *   Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.   *
 *                                                                         *
 ***************************************************************************
}
unit dicteditor;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, fpdatadict,
  Controls, ComCtrls, ExtCtrls, Graphics, Menus, Dialogs, RTTIGrids,
  LazUTF8;

Type
  TEditObjectType = (eotUnknown,eotDictionary,
                 eotTables,eotTable,
                 eotFields,eotField,
                 eotConnection,eotTableData,
                 eotIndexes,eotIndex,
                 eotSequences,eotSequence,
                 eotForeignKeys,eotForeignKey,
                 eotDomains,eotDomain);
Const
  SingleObjectTypes = [eotTable,eotDomain,eotSequence,
                       eotField,eotIndex,eotForeignKey];


Type
  { TDataDictEditor }

  TDataDictEditor = Class(TTabSheet)
  private
    FDD: TFPDataDictionary;
    FImageOffset: Integer;
    FModified: Boolean;
    FTV : TTreeView;
    FEdit : TPanel;
    FSplit : TSplitter;
    FAllowDoubleClick : TEditObjectType;
    FDDNode,
    FTablesNode : TTreeNode;
    FMenu : TPopupMenu;
    FMINewTable,
    FMINewField,
    FMINewIndex,
    FMINewSequence,
    FMINewForeignKey,
    FMINewDomain,
    FMIDeleteObject: TMenuItem;
{$ifndef onlyoldobjects}
    FSequencesNode,
    FDomainsNode : TTreeNode;
{$endif}
    Function AddNewItemPopup(ObjectType: TEditObjectType; AImageIndex : Integer) : TMenuItem;
    procedure CreateGUI;
    procedure DoDoubleClick(Sender: TObject);
    procedure DoNewObject(Sender: TObject);
    procedure DoDeleteObject(Sender: TObject);
    function CurrentObjectWithType(AType: TEditObjectType): TObject;
    function GetCurrentObject: TPersistent;
    Function NewNode (TV : TTreeView;ParentNode : TTreeNode; ACaption : String; AImageIndex : Integer) : TTreeNode;
    Procedure SetCaption;
    Procedure DoSelectNode(Sender : TObject);
    Procedure DoPropertyModified(Sender : TObject);
    Procedure ClearEditor;
    procedure SetModified(const AValue: Boolean);
    procedure UpdateSelectedNode;
    Function GetObjectType(Node : TTreeNode): TEditObjectType;
    Function CreatePropertyGrid(P : TPersistent) : TTIPropertyGrid;
    Function FindNodeWithData(TV : TTreeView; P : Pointer) : TTreeNode;
    Function SelectNextNode (ANode : TTreeNode; ADefault: TTreeNode) : TTreeNode;
    function GetCurrentObjectType: TEditObjectType;
    function GetCurrentField: TDDFieldDef;
    function GetCurrentIndex: TDDIndexDef;
    function GetCurrentTable: TDDTableDef;
{$ifndef onlyoldobjects}
    function GetCurrentSequence: TDDSequenceDef;
    function GetCurrentForeignKey : TDDForeignKeyDef;
    function GetCurrentDomain : TDDDomainDef;
{$endif}
    procedure DoPopup(Sender: TObject);
    Procedure DeleteGlobalObject(AObject : TObject);
    Procedure DeleteTableObject(AObject : TObject);
    procedure SelectGlobalObjectList(AObjectType: TEditObjectType);
    procedure SelectTableObjectList(AObjectType: TEditObjectType; ATableDef : TDDTableDef);
    Procedure SelectSingleObject(AObject : TPersistent);
    procedure GetTableObjectsList(ATabledef: TDDTableDef; AObjectType: TEditObjectType; List: TStrings);
    procedure GetGlobalObjectsList(AObjectType: TEditObjectType; List: TStrings);
    procedure ShowSubLists(TV: TTreeView; ParentNode: TTreeNode; AObject: TObject);
    procedure ShowTableObjectList(TV: TTreeView; ParentNode: TTreeNode; ATableDef: TDDTableDef; AObjectType: TEditObjectType);
    procedure ShowGlobalObjectList(TV: TTreeView; ParentNode: TTreeNode; AObjectType: TEditObjectType; AShowSubLists : Boolean = False);
  Protected
    procedure CreateHandle; override;
  Public
    Constructor Create(AOwner : TComponent); override;
    Destructor Destroy; override;
    // General methods.
    Procedure ShowDictionary;
    Procedure LoadFromFile(AFileName : String);
    Procedure SaveToFile(AFileName : String);
    Procedure CreateCode;
    // New items.
    function  NewGlobalObject(AObjectName: String; AObjectType: TEditObjectType): TObject;
    function  NewTableObject(AObjectName: String; TD: TDDTableDef;AObjectType: TEditObjectType): TObject;
    Procedure NewField(AFieldName : String; TD : TDDTableDef);
    Procedure NewIndex(AIndexName : String; TD : TDDTableDef);
    Procedure NewForeignKey(AKeyName : String; TD : TDDTableDef);
    // Delete items
{$ifndef onlyoldobjects}
    Procedure DeleteSequence(SD : TDDSequenceDef);
    Procedure DeleteDomain(DD : TDDDomainDef);
    Procedure DeleteForeignKey(KD : TDDForeignKeyDef);
{$endif onlyoldobjects}
    Procedure DeleteTable(TD : TDDTableDef);
    Procedure DeleteField(FD : TDDFieldDef);
    Procedure DeleteIndex(ID : TDDIndexDef);
    Procedure DeleteCurrentObject;
    // Properties
    Property DataDictionary : TFPDataDictionary Read FDD;
    Property Modified : Boolean Read FModified Write SetModified;
    Property ImageOffset : Integer Read FImageOffset Write FImageOffset;
    Property CurrentObject : TPersistent Read GetCurrentObject;
    Property ObjectType : TEditObjectType Read GetCurrentObjectType;
    Property CurrentTable : TDDTableDef Read GetCurrentTable;
    Property CurrentField : TDDFieldDef Read GetCurrentField;
    Property CurrentIndex : TDDIndexDef Read GetCurrentIndex;
{$ifndef onlyoldobjects}
    Property CurrentSequence : TDDSequenceDef Read GetCurrentSequence;
    Property CurrentDomain : TDDDomainDef Read GetCurrentDomain;
    Property CurrentForeignKey : TDDForeignKeyDef Read GetCurrentForeignKey;
{$endif onlyoldobjects}
  end;
  

Const
  // Image index, referring to ImgDatamodule.AppImages in unit dmImages;
//  iiDataDict     = 32;  // listed in unit dmImages
  iiTables       = 1;
  iiTable        = 2;
  iiFields       = 3;
  iiField        = 4;
  iiConnection   = 0;
  iiTableData    = 7;
  iiIndexes      = 5;
  iiIndex        = 27;
  iiSequences    = 6;
  iiSequence     = 23;
  iiForeignkeys  = 46;
  iiForeignKey   = 25;
  iiDomains      = 47;
  iiDomain       = 21;
  iiDelete       = 16;

implementation

uses DB, MemDS, fpcodegenerator, TypInfo, dmImages, lazdatadeskstr;

Function ObjectTypeName(ObjectType : TEditObjectType) : String;

Var
  S : String;

begin
  Case ObjectType of
    eotTable      : S:=STable;
    eotField      : S:=SField;
    eotIndex      : S:=SIndex;
    eotSequence   : S:=SSequence;
    eotForeignKey : S:=SForeignKey;
    eotDomain     : S:=SDomain
  else
    Raise EDataDict.CreateFmt(SErrUnknownType,[Ord(ObjectType)]);
  end;
  Result:=S;
end;

Function NewObjectTypeName(ObjectType : TEditObjectType) : String;

Var
  S : String;

begin
  Case ObjectType of
    eotTable      : S:=SNewTableDE;
    eotField      : S:=SNewFieldDE;
    eotIndex      : S:=SNewIndexDE;
    eotSequence   : S:=SNewSequenceDE;
    eotForeignKey : S:=SNewForeignKeyDE;
    eotDomain     : S:=SNewDomainDE;
  else
    Raise EDataDict.CreateFmt(SErrUnknownType,[Ord(ObjectType)]);
  end;
  Result:=S;
end;

Function CreateDatasetFromTabledef(TD : TDDTableDef;AOwner : TComponent = Nil) : TDataset;

Var
  MDS : TMemDataset;
  I : Integer;
  FDD : TDDFieldDef;

begin
  MDS:=TMemDataset.Create(AOwner);
  try
    For I:=0 to TD.Fields.Count-1 do
      begin
      FDD:=TD.Fields[i];
      MDS.FieldDefs.Add(FDD.FieldName,FDD.FieldType);
      end;
    MDS.CreateTable;
    MDS.Open;
  except
    MDS.Free;
    Raise;
  end;
  Result:=MDS;
end;


{ TDataDictEditor }

function TDataDictEditor.NewNode(TV : TTreeView;ParentNode: TTreeNode; ACaption: String; AImageIndex : Integer
  ): TTreeNode;
begin
  Result:=TV.Items.AddChild(ParentNode,ACaption);
  If AImageIndex>=0 then
    begin
    Result.ImageIndex:=FImageOffset+AImageIndex;
    Result.SelectedIndex:=Result.ImageIndex;
    end;
end;

function TDataDictEditor.GetCurrentObjectType: TEditObjectType;
begin
  Result:=GetObjectType(FTV.Selected);
end;

function TDataDictEditor.CurrentObjectWithType(AType : TEditObjectType) : TObject;

Var
  N : TTreeNode;

begin
  Result:=Nil;
  N:=FTV.Selected;
  While (N<>Nil) and (GetObjectType(N)<>AType) do
    N:=N.Parent;
  if (N<>Nil) then
    Result:=TObject(N.Data);
end;

function TDataDictEditor.GetCurrentObject: TPersistent;

Var
  N : TTreeNode;

begin
  Result:=Nil;
  N:=FTV.Selected;
  While (N<>Nil) and Not (GetObjectType(N) in SingleObjectTypes) do
    N:=N.Parent;
  If Assigned(N) then
    Result:=TPersistent(N.Data);
end;

function TDataDictEditor.GetCurrentField: TDDFieldDef;

begin
  Result:=TDDFieldDef(CurrentObjectWithType(eotField));
end;

function TDataDictEditor.GetCurrentIndex: TDDIndexDef;

begin
  Result:=TDDIndexDef(CurrentObjectWithType(eotIndex));
end;

function TDataDictEditor.GetCurrentTable: TDDTableDef;

begin
  Result:=TDDTableDef(CurrentObjectWithType(eotTable));
end;

{$ifndef onlyoldobjects}
function TDataDictEditor.GetCurrentSequence: TDDSequenceDef;

begin
  Result:=TDDSequenceDef(CurrentObjectWithType(eotSequence));
end;

function TDataDictEditor.GetCurrentDomain: TDDDomainDef;

begin
  Result:=TDDDomainDef(CurrentObjectWithType(eotDomain));
end;

function TDataDictEditor.GetCurrentForeignKey: TDDForeignKeyDef;

begin
  Result:=TDDForeignKeyDef(CurrentObjectWithType(eotForeignKey));
end;
{$endif onlyoldobjects}

procedure TDataDictEditor.DoPopup(Sender: TObject);

Var
  B : Boolean;
  EOT : TEditObjectType;

begin
  // Check availablility of items;
  B:=CurrentTable<>Nil;
  FMINewField.Enabled:=B;
  FMINewIndex.Enabled:=B;
  FMINewForeignKey.Enabled:=B;
  EOT:=ObjectType;
  B:=EOT in SingleObjectTypes;
  FMIDeleteObject.Enabled:=B;
  If B then
    FMIDeleteObject.Caption:=Format(SDeleteObject,[ObjectTypeName(EOT)])
  else
    FMIDeleteObject.Caption:=Format(SDeleteObject,[SObject]);
end;


Function TDataDictEditor.AddNewItemPopup(ObjectType : TEditObjectType; AImageIndex : Integer) : TMenuItem;

begin
  Result:=TMenuItem.Create(Self);
  Result.Name:='NewItem'+GetEnumName(TypeInfo(TEditObjectType),Ord(ObjectType));
  Result.Tag:=Ord(ObjectType);
  Result.Caption:=NewObjectTypeName(ObjectType);
  Result.OnClick:=@DoNewObject;
  Result.ImageIndex:=AImageIndex;
  FMenu.Items.Add(Result);
end;

constructor TDataDictEditor.Create(AOwner: TComponent);


begin
  inherited Create(AOwner);
  FDD:=TFPDataDictionary.Create;
  CreateGUI;
end;

Procedure TDataDictEditor.CreateGUI;
Var
  P : TPortableNetworkGraphic;
  I : Integer;
begin
  FEdit:=TPanel.Create(Self);
  FEdit.Parent:=Self;
  FEdit.Name:='FEdit';
  FEdit.Align:=alRight;
  FEdit.Caption:='';
  FEdit.Width:=200;
  FSplit:=TSplitter.Create(Self);
  FSplit.Parent:=Self;
  FSplit.Align:=alRight;
  FTV:=TTreeView.Create(Self);
  FTV.Name:='FTV';
  FTV.Parent:=Self;
  FTV.Align:=alClient;
  FTV.OnSelectionChanged:=@DoSelectNode;
  FTV.ShowLines:=True;
  FMenu:=TPopupMenu.Create(Self);
  FMenu.Name:='FMenu';
  FMenu.OnPopup:=@DoPopup;
  FMINewTable:=AddNewItemPopup(eotTable,iiTable);
  FMINewField:=AddNewItemPopup(eotField,iiField);
  FMINewIndex:=AddNewItemPopup(eotIndex,iiIndex);
  FMINewSequence:=AddNewItemPopup(eotSequence,iiSequence);
  FMINewForeignKey:=AddNewItemPopup(eotForeignKey,iiForeignKey);
  FMINewDomain:=AddNewItemPopup(eotDomain,iiDomain);
  FMenu.Items.Add(NewLine);
  FMIDeleteObject:=TMenuItem.Create(Self);
  FMIDeleteObject.Caption:=Format(SDeleteObject,[SObject]);
  FMIDeleteObject.OnClick:=@DoDeleteObject;
  FMIDeleteObject.ImageIndex:=IIDelete;
  FMenu.Items.Add(FMIDeleteObject);
  FTV.PopupMenu:=FMenu;
  FTV.Images:=ImgDatamodule.AppImages;
  FMenu.Images:=ImgDatamodule.AppImages;
end;

procedure TDataDictEditor.CreateHandle;
begin
  inherited;
  ShowDictionary;
end;

destructor TDataDictEditor.Destroy;
begin
  FreeAndNil(FTV);
  FreeAndNil(FDD);
  inherited Destroy;
end;

procedure TDataDictEditor.ShowDictionary;

var
  S : String;
  
begin
  FTV.Items.BeginUpdate;
  try
    FTV.Items.Clear;
    S:=FDD.Name;
    If (S='') then
      S:=SNodeDataDictionary;
    FDDNode:=NewNode(FTV,Nil,S,iiDataDict);
    FDDNode.Data:=FDD;
    FTablesNode:=NewNode(FTV,FDDNode,SNodeTables,iiTables);
    ShowGlobalObjectList(FTV,FTablesNode,eotTable,True);
{$ifndef onlyoldobjects}
    FSequencesNode:=NewNode(FTV,FDDNode,SNodeSequences,iiSequences);
    ShowGlobalObjectList(FTV,FSequencesNode,eotSequence,True);
    FDomainsNode:=NewNode(FTV,FDDNode,SNodeDomains,iiDomains);
    ShowGlobalObjectList(FTV,FDomainsNode,eotDomain,True);
{$endif onlyoldobjects}
    SetCaption;
    FTV.Selected:=FDDNode;
  finally
    FTV.Items.EndUpdate;
  end;
end;

Function TDataDictEditor.NewGlobalObject(AObjectName: String; AObjectType : TEditObjectType) : TObject;

Var
  II : Integer;
  N,PN : TTreeNode;
begin
  Case AObjectType of
    eotTable :
      begin
      Result:=FDD.Tables.AddTable(AObjectName);
      II:=IITable;
      PN:=FTablesNode;
      end;
{$ifndef onlyoldobjects}
    eotSequence :
      begin
      Result:=FDD.Sequences.AddSequence(AObjectName);
      II:=IISequence;
      PN:=FSequencesNode;
      end;
    eotDomain :
      begin
      Result:=FDD.Domains.AddDomain(AObjectName);
      II:=IIDomain;
      PN:=FDomainsNode;
      end;
{$endif onlyoldobjects}
  end;
  N:=NewNode(FTV,PN,AObjectName,II);
  N.Data:=Result;
  FTV.Selected:=N;
  ShowSubLists(FTV,N,Result);
  Modified:=True;
end;


Function TDataDictEditor.NewTableObject(AObjectName: String; TD: TDDTableDef; AObjectType : TEditObjectType) : TObject;

Var
  TN : TTreeNode;
  POT : TEditObjectType;
  II : Integer;

begin
  Case AObjectType of
    eotField         :
      begin
      POT:=eotFields;
      Result:=TD.Fields.AddField(AObjectName);
      II:=IIfield;
      end;
{$ifndef onlyoldobjects}
    eotIndex:
      begin
      POT:=eotIndexes;
      Result:=TD.Indexes.AddIndex(AObjectName);
      II:=IIIndex;
      end;
    eotForeignKey :
      begin
      POT:=eotForeignKeys;
      Result:=TD.foreignKeys.AddForeignKeyDef(AObjectName);
      II:=IIForeignkey;
      end;
{$endif onlyoldobjects}
  end;
  TN:=FindNodeWithData(FTV,TD);
  TN:=TN.GetFirstChild;
  While (TN<>Nil) and (GetObjectType(TN)<>POT) do
    TN:=TN.GetNextSibling;
  If (TN<>Nil) then
    begin
    TN:=NewNode(FTV,TN,AObjectName,II);
    TN.Data:=Result;
    FTV.Selected:=TN;
    Modified:=True;
    end
  else
    FreeAndNil(Result); // Error !!
end;


procedure TDataDictEditor.NewField(AFieldName: String; TD: TDDTableDef);

begin
  NewTableObject(AFieldName,TD,eotField);
end;

procedure TDataDictEditor.NewIndex(AIndexName: String; TD: TDDTableDef);

begin
  NewTableObject(AIndexName,TD,eotIndex);
end;

procedure TDataDictEditor.NewForeignKey(AKeyName: String; TD: TDDTableDef);

begin
  NewTableObject(AKeyName,TD,eotForeignKey);
end;

procedure TDataDictEditor.SetCaption;

Var
  S : String;
  
begin
  If (FDD.Name<>'') then
    S:=FDD.Name
  else
    S:=ChangeFileExt(ExtractFileName(FDD.FileName),'');
  If (S='') then
    S:=SNewDictionary;
  if FModified then
    S:=S+' *';
  Caption:=S;
end;

procedure TDataDictEditor.DoSelectNode(Sender: TObject);

Var
  N : TTreeNode;
  O,OP : TObject;
  
begin
  N:=FTV.Selected;
  If N=Nil then
    exit;
  O:=TObject(N.Data);
  If Assigned(N.Parent) then
    OP:=TObject(N.Parent.Data);
  Case ObjectType of
    eotUnknown    : ;
    eotDictionary : SelectSingleObject(FDD);
    eotTables     : SelectGlobalObjectList(eotTable);
    eotTable      : SelectSingleObject(O as TPersistent);
    eotFields     : SelectTableObjectList(eotField,OP as TDDTableDef);
    eotField      : SelectSingleObject(O as TPersistent);
{$ifndef onlyoldobjects}
    eotIndexes    : SelectTableObjectList(eotIndex,OP as TDDTableDef);
    eotIndex      : SelectSingleObject(O as TPersistent);
    eotDomains      : SelectGlobalObjectList(eotDomain);
    eotDomain       : SelectSingleObject(O as TPersistent);
    eotSequences    : SelectGlobalObjectList(eotSequence);
    eotSequence     : SelectSingleObject(O as TPersistent);
    eotForeignKeys  : SelectTableObjectList(eotForeignKey,OP as TDDTableDef);
    eotForeignKey   : SelectSingleObject(O as TPersistent);
{$endif onlyoldobjects}
  end;
end;

procedure TDataDictEditor.DoPropertyModified(Sender: TObject);
begin
  Modified:=True;
  UpdateSelectedNode;
end;

procedure TDataDictEditor.UpdateSelectedNode;

Var
  N : TTreeNode;
  
begin
  N:=FTV.Selected;
  If (N.Data=Nil) then
    Exit;
  With N do
    Case ObjectType of
      eotField         : Text:=TDDFieldDef(N.Data).FieldName;
      eotDictionary    : Text:=TFPDataDictionary(N.Data).Name;
      eotTable         : Text:=TDDTableDef(N.Data).TableName;
{$ifndef onlyoldobjects}
      eotSequence   : Text:=TDDSequenceDef(N.Data).SequenceName;
      eotDomain     : Text:=TDDDomainDef(N.Data).DomainName;
      eotForeignKey : Text:=TDDForeignkeyDef(N.Data).KeyName;
{$endif onlyoldobjects}
    end;
end;

procedure TDataDictEditor.SelectGlobalObjectList(AObjectType : TEditObjectType);

Var
  TV : TTreeView;

begin
  ClearEditor;
  TV:=TTreeView.Create(Self);
  TV.ShowLines:=True;
  TV.Parent:=FEdit;
  TV.Align:=alClient;
  TV.Images := ImgDataModule.AppImages;
  ShowGlobalObjectList(TV,Nil,AObjectType,False);
  TV.OnDblClick:=@DoDoubleClick;
  FAllowDoubleClick:=AObjectType;
end;

procedure TDataDictEditor.SelectTableObjectList(AObjectType: TEditObjectType; ATableDef : TDDTableDef);

Var
  TV : TTreeView;

begin
  ClearEditor;
  TV:=TTreeView.Create(Self);
  TV.ShowLines:=True;
  TV.Parent:=FEdit;
  TV.Align:=alClient;
  TV.Images := ImgDatamodule.AppImages;
  ShowTableObjectList(TV,Nil,ATableDef,AObjectType);
  TV.OnDblClick:=@DoDoubleClick;
  FAllowDoubleClick:=AObjectType;
end;

procedure TDataDictEditor.DoDoubleClick(Sender : TObject);

Var
  TV : TTreeView;
  N: TTreeNode;

begin
  TV:=Sender As TTreeView;
  N:=TV.Selected;
  If (GetObjectType(N)=FAllowDoubleClick) and (N.Data<>Nil) then
    FTV.Selected:=FindNodeWithData(FTV,N.Data);
end;

procedure TDataDictEditor.DoNewObject(Sender: TObject);

Var
  EOT : TEditObjectType;
  S,N : String;

begin
  EOT:=TEditObjectType((Sender as TMenuItem).Tag);
  S:=ObjectTypeName(EOT);
  N:='';
  if InputQuery(Format(SNewObject,[S]),Format(SNameFor,[S]),N) then
    begin
    case EOT of
      eotField : NewField(N,CurrentTable);
      eotIndex : NewIndex(N,CurrentTable);
      eotForeignKey : NewForeignKey(N,CurrentTable);
    else
      NewGlobalObject(N,EOT);
    end;
    end;
end;

procedure TDataDictEditor.DoDeleteObject(Sender: TObject);
begin
  DeleteCurrentObject;
end;

function TDataDictEditor.SelectNextNode(ANode: TTreeNode; ADefault : TTreeNode): TTreeNode;

Var
  NN : TTreeNode;

begin
  NN:=ANode.GetNextSibling;
  If (NN=Nil) then
    begin
    NN:=ANode.GetPrevSibling;
    If (NN=Nil) then
      if Assigned(ADefault) then
        NN:=ADefault
      else
        begin
        NN:=Anode.Parent;
        If Assigned(NN) then
          NN:=NN.Parent;
        end;
    end;
  ANode.Free;
  FTV.Selected:=NN;
  Result:=NN;
end;

procedure TDataDictEditor.SetModified(const AValue: Boolean);
begin
  FModified:=AValue;
  SetCaption;
end;

Function TDataDictEditor.FindNodeWithData(TV : TTreeView; P : Pointer) : TTreeNode;

Var
  I : Integer;

begin
  I:=0;
  Result:=Nil;
  While (Result=Nil) and (I<TV.Items.Count) do
    begin
    if (TV.Items[i].Data=P) then
      Result:=TV.Items[i];
    Inc(i);
    end;
end;

procedure TDataDictEditor.ClearEditor;

begin
  With FEdit do
    While (ControlCount>0) do
      Controls[ControlCount-1].Free;
end;

procedure TDataDictEditor.SelectSingleObject(AObject: TPersistent);
begin
  ClearEditor;
  CreatePropertyGrid(AObject);
end;

Function TDataDictEditor.CreatePropertyGrid(P : TPersistent) : TTIPropertyGrid;

begin
  Result:=TTIPropertyGrid.Create(Self);
  With Result do
    begin
    Parent:=FEdit;
    Align:=alClient;
    TIObject:=P;
    OnModified:=@DoPropertyModified;
    end;
end;

function TDataDictEditor.GetObjectType(Node: TTreeNode): TEditObjectType;

Var
  I : Integer;
  
begin
  Result:=eotUnknown;
  If Node<>Nil then
    begin
    I:=Node.ImageIndex;
    I:=I-ImageOffset+1;
    If (I>=0) and (I<=Ord(High(TEditObjectType))) then
      Result:=TEditObjectType(I);
    end;
end;

procedure TDataDictEditor.GetTableObjectsList(ATabledef :TDDTableDef; AObjectType : TEditObjectType; List : TStrings);

Var
  I : Integer;

begin
  Case AObjectType of
    eotField :  For I:=0 to ATableDef.Fields.Count-1 do
                 List.AddObject(ATableDef.Fields[i].FieldName,ATableDef.Fields[i]);
{$ifndef onlyoldobjects}
    eotIndex :  For I:=0 to ATableDef.Indexes.Count-1 do
                 List.AddObject(ATableDef.Indexes[i].IndexName,ATableDef.Indexes[i]);
    eotForeignKey :  For I:=0 to ATableDef.ForeignKeys.Count-1 do
                 List.AddObject(ATableDef.ForeignKeys[i].KeyName,ATableDef.ForeignKeys[i]);
{$endif onlyoldobjects}
  end;
  If List is TStringList then
    TStringList(List).Sorted:=True;
end;

procedure TDataDictEditor.ShowTableObjectList(TV : TTreeView; ParentNode: TTreeNode; ATableDef: TDDTableDef;AObjectType : TEditObjectType);

Var
  TN : TTreeNode;
  TL : TStringList;
  II, I : Integer;

begin
  TL:=TStringList.Create;
  Try
    Case AObjectType of
      eotField : II:=iiField;
{$ifndef onlyoldobjects}
      eotIndex : II:=iiIndex;
      eotForeignKey : II:=iiForeignKey;
{$endif}
    end;
    GetTableObjectsList(ATableDef,AObjectType,TL);
    For I:=0 to TL.Count-1 do
      begin
      TN:=NewNode(TV,ParentNode,TL[i],II);
      TN.Data:=TL.Objects[i];
      end;
    If Assigned(ParentNode) then
      ParentNode.Expand(False);
  Finally
    FreeAndNil(TL);
  end;
end;

procedure TDataDictEditor.GetGlobalObjectsList(AObjectType: TEditObjectType;
  List: TStrings);

Var
  I : Integer;

begin
  Case AObjectType of
    eotTable:
      For I:=0 to FDD.Tables.Count-1 do
        List.AddObject(FDD.Tables[i].TableName,FDD.Tables[i]);
{$ifndef onlyoldobjects}
    eotSequence:
      For I:=0 to FDD.Sequences.Count-1 do
        List.AddObject(FDD.Sequences[i].SequenceName,FDD.Sequences[i]);

    eotDomain:
      For I:=0 to FDD.Domains.Count-1 do
        List.AddObject(FDD.Domains[i].DomainName,FDD.Domains[i]);
{$endif onlyoldobjects}
  end;
  If List is TStringList then
    TStringList(List).Sorted:=True;
end;

procedure TDataDictEditor.ShowSubLists(TV: TTreeView; ParentNode: TTreeNode; AObject : TObject);

Var
  TD : TDDTableDef;
  N : TTreeNode;
begin
  If AObject is TDDTableDef then
    begin
    TD:=AObject as TDDTableDef;
    N:=NewNode(TV,ParentNode,SNodeFields,iiFields);
    ShowTableObjectList(TV,N,TD,eotField);
{$ifndef onlyoldobjects}
    N:=NewNode(TV,ParentNode,SNodeIndexes,iiIndexes);
    ShowTableObjectList(TV,N,TD,eotIndex);
    N:=NewNode(TV,ParentNode,SNodeForeignKeys,iiForeignKeys);
    ShowTableObjectList(TV,N,TD,eotForeignKey);
{$endif onlyoldobjects}
    end;
end;

procedure TDataDictEditor.ShowGlobalObjectList(TV: TTreeView;
  ParentNode: TTreeNode; AObjectType: TEditObjectType; AShowSubLists : Boolean = False);

Var
  TN : TTreeNode;
  TL : TStringList;
  II, I : Integer;

begin
  TL:=TStringList.Create;
  Try
    Case AObjectType of
      eotTable    : II:=iiTable;
{$ifndef onlyoldobjects}
      eotSequence : II:=iiSequence;
      eotDomain   : II:=iiDomain;
{$endif onlyoldobjects}
    end;
    GetGlobalObjectsList(AObjectType,TL);
    For I:=0 to TL.Count-1 do
      begin
      TN:=NewNode(TV,ParentNode,TL[i],II);
      TN.Data:=TL.Objects[i];
      If AShowSubLists then
        ShowSubLists(TV,TN,TL.Objects[i]);
      end;
    If Assigned(ParentNode) then
      ParentNode.Expand(False);
  Finally
    FreeAndNil(TL);
  end;
end;

procedure TDataDictEditor.DeleteGlobalObject(AObject: TObject);

Var
  N : TTreeNode;

begin
  N:=FindNodeWithData(FTV,Pointer(AObject));
  SelectNextNode(N,FDDNode);
  AObject.Free;
  Modified:=True;
end;

procedure TDataDictEditor.DeleteTableObject(AObject: TObject);

Var
  N : TTreeNode;

begin
  N:=FindNodeWithData(FTV,Pointer(AObject));
  SelectNextNode(N,Nil);
  AObject.Free;
  Modified:=True;
end;

procedure TDataDictEditor.LoadFromFile(AFileName: String);
begin
  FDD.LoadFromFile(UTF8ToSys(AFileName));
  ShowDictionary;
  SetCaption;
end;

procedure TDataDictEditor.SaveToFile(AFileName: String);
begin
  With FDD do
    begin
    If (Name='') then
      Name:=ChangeFileExt(ExtractFileName(AFileName),'');
    SaveToFile(AFileName);
    end;
  Modified:=False;
end;

procedure TDataDictEditor.DeleteTable(TD: TDDTableDef);

begin
  DeleteGlobalObject(TD);
end;

procedure TDataDictEditor.DeleteField(FD: TDDFieldDef);

begin
  DeleteTableObject(FD);
end;

procedure TDataDictEditor.DeleteIndex(ID: TDDIndexDef);

begin
  DeleteTableObject(ID);
end;

procedure TDataDictEditor.DeleteCurrentObject;

Var
  N : TTreeNode;

begin
  N:=FTV.Selected;
  If GetCurrentObjectType in [eotField,eotIndex,eotForeignKey] then
    DeleteTableObject(TObject(N.Data))
  else
    DeleteGlobalObject(TObject(N.Data))
end;

{$ifndef onlyoldobjects}
procedure TDataDictEditor.DeleteSequence(SD: TDDSequenceDef);

begin
  DeleteGlobalObject(SD);
end;

procedure TDataDictEditor.DeleteDomain(DD: TDDDomainDef);

begin
  DeleteGlobalObject(DD);
end;

procedure TDataDictEditor.DeleteForeignKey(KD: TDDForeignKeyDef);

begin
  DeleteTableObject(KD);
end;
{$endif onlyoldobjects}

procedure TDataDictEditor.CreateCode;

Var
  TD : TDDTableDef;
  DS : TDataset;

begin
  TD:=CurrentTable;
  If Not assigned(TD) then
    exit;
  DS:=CreateDatasetFromTabledef(TD,Self);
  try
    With TFPCodeGenerator.Create(DS) do
      try
        DataSet:=DS;
        TableNameHint:=TD.TableName;
        Execute;
      Finally
        Free;
      end;
  finally
    DS.Free;
  end;
end;

end.

