unit Aurelius.Plugin.ExportForm;

interface

uses
  Generics.Collections, Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, UITypes, ComCtrls, ExtCtrls, Vcl.Grids,
  Buttons, Menus, Vcl.ActnList,

  Bcl.Json, Bcl.Json.Classes, Bcl.Json.Serializer, Bcl.Json.Writer,
  Aurelius.Plugin.Types,
  Aurelius.Plugin.Options,
  Aurelius.Plugin.Import,
  Aurelius.Plugin.Database, Vcl.CheckLst, Data.DB, Vcl.DBGrids,
  Aurelius.Bind.BaseDataset, Aurelius.Bind.Dataset;

type
  TTable = class;

  TSelectionType = (stAll, stNone, stInvert);

  TFileStateFlag = (sfOpened, sfModified);

  TFileStateFlags = set of TFileStateFlag;

  TfmAureliusExport = class(TForm)
    pageControlOptions: TPageControl;
    tsAdvancedSettings: TTabSheet;
    GroupBox1: TGroupBox;
    edTableNameFormat: TEdit;
    GroupBox2: TGroupBox;
    cbAssociationNameSource: TComboBox;
    edAssociationNameFormat: TEdit;
    GroupBox3: TGroupBox;
    edFieldNameFormat: TEdit;
    GroupBox4: TGroupBox;
    cbManyValuedNameSource: TComboBox;
    edManyValuedNameFormat: TEdit;
    GroupBox5: TGroupBox;
    cbDefaultOneToOneMapping: TComboBox;
    cbDefaultAssociationFetchMode: TComboBox;
    cbDefaultManyValuedFetchMode: TComboBox;
    edDefaultAncestorClass: TEdit;
    btCancel: TBitBtn;
    btOk: TBitBtn;
    GroupBox6: TGroupBox;
    edDictionaryName: TEdit;
    GroupBox7: TGroupBox;
    cbGenerateDictionary: TCheckBox;
    cbTableNameSingularize: TCheckBox;
    cbManyValuedNameSingularize: TCheckBox;
    edDefaultDynPropContainer: TEdit;
    cbCheckSequences: TComboBox;
    cbDefaultAssociationCascade: TComboBox;
    cbRegisterEntities: TCheckBox;
    cbFieldNameCamelCase: TCheckBox;
    cbAssociationNameCamelCase: TCheckBox;
    cbManyValuedNameCamelCase: TCheckBox;
    cbTableNameCamelCase: TCheckBox;
    cbNoNullable: TCheckBox;
    cbTableNameRemoveUnderline: TCheckBox;
    cbFieldNameRemoveUnderline: TCheckBox;
    cbAssociationNameRemoveUnderline: TCheckBox;
    cbManyValuedNameRemoveUnderline: TCheckBox;
    Label3: TLabel;
    Label4: TLabel;
    Label5: TLabel;
    Label6: TLabel;
    Label7: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Label17: TLabel;
    tsPreview: TTabSheet;
    memoSource: TMemo;
    tsMapping: TTabSheet;
    dsTables: TDataSource;
    adsTables: TAureliusDataset;
    adsTablesName: TStringField;
    adsTablesChecked: TBooleanField;
    GroupBox8: TGroupBox;
    gridTables: TDBGrid;
    Panel1: TPanel;
    Label18: TLabel;
    lbSelectAll: TLabel;
    lbSelectNone: TLabel;
    lbInvertSelection: TLabel;
    lbSelectedTablesCount: TLabel;
    MainMenu1: TMainMenu;
    Configuration1: TMenuItem;
    Load1: TMenuItem;
    Save1: TMenuItem;
    N1: TMenuItem;
    Resettodefaultvalues1: TMenuItem;
    ConfigSaveDialog: TSaveDialog;
    ActionList1: TActionList;
    actLoadConfig: TAction;
    actSaveConfig: TAction;
    actResetConfig: TAction;
    Saveas1: TMenuItem;
    actSaveConfigAs: TAction;
    ConfigOpenDialog: TOpenDialog;
    procedure btOkClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure SaveProperty(Sender: TObject);
    procedure pageControlOptionsChange(Sender: TObject);
    procedure pageControlOptionsChanging(Sender: TObject;
      var AllowChange: Boolean);
    procedure gridTablesDrawColumnCell(Sender: TObject; const Rect: TRect;
      DataCol: Integer; Column: TColumn; State: TGridDrawState);
    procedure gridTablesKeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure gridTablesCellClick(Column: TColumn);
    procedure FormResize(Sender: TObject);
    procedure lbSelectAllMouseEnter(Sender: TObject);
    procedure lbSelectAllMouseLeave(Sender: TObject);
    procedure lbSelectAllClick(Sender: TObject);
    procedure lbSelectNoneClick(Sender: TObject);
    procedure lbInvertSelectionClick(Sender: TObject);
    procedure adsTablesAfterOpen(DataSet: TDataSet);
    procedure adsTablesAfterPost(DataSet: TDataSet);
    procedure FormShow(Sender: TObject);
    procedure actResetConfigExecute(Sender: TObject);
    procedure actLoadConfigExecute(Sender: TObject);
    procedure actSaveConfigAsExecute(Sender: TObject);
    procedure actSaveConfigExecute(Sender: TObject);
    procedure actSaveConfigUpdate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    FDictionary: HDICTIONARY;
    FDatabase: IPluginDatabase;
    FTables: TList<TTable>;
    FOptions: IASPluginOptions;
    FLoading: integer;
    FSource: string;
    FConfigFileName: string;
    FConfigFileState: TFileStateFlags;
    FFormCaption: string;
    function GetFormCaption: string;
    procedure LoadForm;
    procedure LoadProperties(AContainer: TWinControl);
    procedure LoadProperty(Sender: TObject);
    procedure LoadExcludedTables;
    procedure InvertSelectionOfCurrentRecord;
    procedure UpdatePreview;
    procedure AdjustColumnWidths(Grid: TDBGrid);
    function GenerateSource: string;
    procedure SaveConfigFile;

    // selection
    procedure SelectTables(Selection: TSelectionType);
    procedure SelectAll;
    procedure UnselectAll;
    procedure InvertSelection;
    function GetSelectedTablesCount: integer;
    procedure SetSelectedTablesText;

    //
    function HasFileOpened: Boolean;
    function CanCloseFile: Boolean;
  public
    constructor Create(ADictionary: HDICTIONARY; ADatabase: IPluginDatabase;
      AOptions: IASPluginOptions); reintroduce;
    destructor Destroy; override;
    property Source: string read FSource;
  end;

  TTable = class
  strict private
    FChecked: Boolean;
    FName: string;
  public
    constructor Create(AName: string; AChecked: boolean = True);
    property Checked: boolean read FChecked write FChecked;
    property Name: string read FName;
  end;

  EAureliusPluginConfigException = class(Exception);

  procedure SaveConfig(Options: IASPluginOptions; Config: TJObject);
  procedure LoadConfig(Options: IASPluginOptions; Config: TJObject);
  procedure ResetConfig(Config: TJObject);

implementation

{$R *.dfm}

procedure SaveConfig(Options: IASPluginOptions; Config: TJObject);

  procedure AddMember(const Name: string; const Value: WideString); overload;
  begin
    Config[Name] := TJPrimitive.Create(Value);
  end;

  procedure AddMember(const Name: string; const Value: Boolean); overload;
  begin
    Config[Name] := TJPrimitive.Create(Value);
  end;

  procedure AddMember(const Name: string; const Value: Integer); overload;
  begin
    Config[Name] := TJPrimitive.Create(Value);
  end;

  function HasExcludedTable(ATableName: string): boolean;
  var
    I: integer;
  begin
    Result := False;
    for I := 0 to Config['ExcludedTables'].AsArray.Count - 1 do
    begin
      if Config['ExcludedTables'].AsArray[I].AsString = ATableName then
      begin
        Result := True;
        Break;
      end;
    end;
  end;

  procedure DeleteFromExcludedTables(ATableName: string);
  var
    I: integer;
  begin
    for I := Config['ExcludedTables'].AsArray.Count - 1 downto 0 do
    begin
      if Config['ExcludedTables'].AsArray[I].AsString = ATableName then
        Config['ExcludedTables'].AsArray.Delete(I);
    end;
  end;

var
  TableConfig: ITableConfig;
  I: integer;
begin
  AddMember('TableNameFormat', Options.TableNameFormat);
  AddMember('TableNameSingularize', Options.TableNameSingularize);
  AddMember('TableNameCamelCase', Options.TableNameCamelCase);
  AddMember('TableNameRemoveUnderline', Options.TableNameRemoveUnderline);
  AddMember('FieldNameFormat', Options.FieldNameFormat);
  AddMember('FieldNameCamelCase', Options.FieldNameCamelCase);
  AddMember('FieldNameRemoveUnderline', Options.FieldNameRemoveUnderline);
  AddMember('AssociationNameSource', Options.AssociationNameSource);
  AddMember('AssociationNameFormat', Options.AssociationNameFormat);
  AddMember('AssociationNameCamelCase', Options.AssociationNameCamelCase);
  AddMember('AssociationNameRemoveUnderline', Options.AssociationNameRemoveUnderline);
  AddMember('ManyValuedNameSource', Options.ManyValuedNameSource);
  AddMember('ManyValuedNameFormat', Options.ManyValuedNameFormat);
  AddMember('ManyValuedNameSingularize', Options.ManyValuedNameSingularize);
  AddMember('ManyValuedNameCamelCase', Options.ManyValuedNameCamelCase);
  AddMember('ManyValuedNameRemoveUnderline', Options.ManyValuedNameRemoveUnderline);
  AddMember('OmitDictionary', Options.OmitDictionary);
  AddMember('DictionaryName', Options.DictionaryName);
  AddMember('RegisterEntities', Options.RegisterEntities);
  AddMember('NoNullable', Options.NoNullable);
  AddMember('DefaultAssociationFetchMode', Options.DefaultAssociationFetchMode);
  AddMember('DefaultManyValuedFetchMode', Options.DefaultManyValuedFetchMode);
  AddMember('DefaultAssociationCascadeDefinition', Options.DefaultAssociationCascadeDefinition);
  AddMember('DefaultOneToOneMapping', Options.DefaultOneToOneMapping);
  AddMember('DefaultAncestorClass', Options.DefaultAncestorClass);
  AddMember('DefaultDynPropContainer', Options.DefaultDynPropContainer);
  AddMember('CheckSequencesMode', Options.CheckSequencesMode);

  // Tables
  if not Config.Contains('ExcludedTables') then
    Config['ExcludedTables'] := TJArray.Create;
  if Config['ExcludedTables'].IsNull then
    raise EAureliusPluginConfigException.Create('Configuration is not valid');
  if not Config['ExcludedTables'].IsArray then
    raise EAureliusPluginConfigException.Create('Configuration is not valid');
  for I := 0 to Options.TableConfigCount - 1 do
  begin
    TableConfig := Options.GetTableConfigByIndex(I);
    if (not TableConfig.Checked) and (not HasExcludedTable(TableConfig.Name)) then
      Config['ExcludedTables'].AsArray.Add(TableConfig.Name)
    else if TableConfig.Checked and HasExcludedTable(TableConfig.Name) then
      DeleteFromExcludedTables(TableConfig.Name);
  end;
end;

procedure LoadConfig(Options: IASPluginOptions; Config: TJObject);

  function MemberValue(const Name: string): string;
  begin
    Result := Config[Name].AsString;
  end;

  function MemberBoolValue(const Name: string): boolean;
  begin
    Result := Config[Name].AsBoolean;
  end;

  function MemberIntValue(const Name: string): integer;
  begin
    Result := Config[Name].AsInteger;
  end;

var
  TableName: string;
  TableConfig: ITableConfig;
  I: Integer;
begin
  if Config.Contains('TableNameFormat') then
    Options.TableNameFormat := MemberValue('TableNameFormat');
  if Config.Contains('TableNameSingularize') then
    Options.TableNameSingularize := MemberBoolValue('TableNameSingularize');
  if Config.Contains('TableNameCamelCase') then
    Options.TableNameCamelCase := MemberBoolValue('TableNameCamelCase');
  if Config.Contains('TableNameRemoveUnderline') then
    Options.TableNameRemoveUnderline := MemberBoolValue('TableNameRemoveUnderline');
  if Config.Contains('FieldNameFormat') then
    Options.FieldNameFormat := MemberValue('FieldNameFormat');
  if Config.Contains('FieldNameCamelCase') then
    Options.FieldNameCamelCase := MemberBoolValue('FieldNameCamelCase');
  if Config.Contains('FieldNameRemoveUnderline') then
    Options.FieldNameRemoveUnderline := MemberBoolValue('FieldNameRemoveUnderline');
  if Config.Contains('AssociationNameSource') then
    Options.AssociationNameSource := MemberIntValue('AssociationNameSource');
  if Config.Contains('AssociationNameFormat') then
    Options.AssociationNameFormat := MemberValue('AssociationNameFormat');
  if Config.Contains('AssociationNameCamelCase') then
    Options.AssociationNameCamelCase := MemberBoolValue('AssociationNameCamelCase');
  if Config.Contains('AssociationNameRemoveUnderline') then
    Options.AssociationNameRemoveUnderline := MemberBoolValue('AssociationNameRemoveUnderline');
  if Config.Contains('ManyValuedNameSource') then
    Options.ManyValuedNameSource := MemberIntValue('ManyValuedNameSource');
  if Config.Contains('ManyValuedNameFormat') then
    Options.ManyValuedNameFormat := MemberValue('ManyValuedNameFormat');
  if Config.Contains('ManyValuedNameSingularize') then
    Options.ManyValuedNameSingularize := MemberBoolValue('ManyValuedNameSingularize');
  if Config.Contains('ManyValuedNameCamelCase') then
    Options.ManyValuedNameCamelCase := MemberBoolValue('ManyValuedNameCamelCase');
  if Config.Contains('ManyValuedNameRemoveUnderline') then
    Options.ManyValuedNameRemoveUnderline := MemberBoolValue('ManyValuedNameRemoveUnderline');
  if Config.Contains('OmitDictionary') then
    Options.OmitDictionary := MemberBoolValue('OmitDictionary');
  if Config.Contains('DictionaryName') then
    Options.DictionaryName := MemberValue('DictionaryName');
  if Config.Contains('RegisterEntities') then
    Options.RegisterEntities := MemberBoolValue('RegisterEntities');
  if Config.Contains('NoNullable') then
    Options.NoNullable := MemberBoolValue('NoNullable');
  if Config.Contains('DefaultAssociationFetchMode') then
    Options.DefaultAssociationFetchMode := MemberIntValue('DefaultAssociationFetchMode');
  if Config.Contains('DefaultManyValuedFetchMode') then
    Options.DefaultManyValuedFetchMode := MemberIntValue('DefaultManyValuedFetchMode');
  if Config.Contains('DefaultAssociationCascadeDefinition') then
    Options.DefaultAssociationCascadeDefinition := MemberIntValue('DefaultAssociationCascadeDefinition');
  if Config.Contains('DefaultOneToOneMapping') then
    Options.DefaultOneToOneMapping := MemberIntValue('DefaultOneToOneMapping');
  if Config.Contains('DefaultAncestorClass') then
    Options.DefaultAncestorClass := MemberValue('DefaultAncestorClass');
  if Config.Contains('DefaultDynPropContainer') then
    Options.DefaultDynPropContainer := MemberValue('DefaultDynPropContainer');
  if Config.Contains('CheckSequencesMode') then
    Options.CheckSequencesMode := MemberIntValue('CheckSequencesMode');

  // Tables
  Options.CheckAllTableConfig(True);
  if Config.Contains('ExcludedTables') and (not Config['ExcludedTables'].IsNull) then
  begin
    if not Config['ExcludedTables'].IsArray then
      raise EAureliusPluginConfigException.Create('Configuration is not valid');

    for I := 0 to Config['ExcludedTables'].AsArray.Count - 1 do
    begin
      TableName := Config['ExcludedTables'].AsArray[I].AsString;
      TableConfig := Options.FindTableConfigByName(TableName);
      if TableConfig = nil then
        TableConfig := Options.AddTableConfig(TableName);
      TableConfig.Checked := False;
    end;
  end;
end;

procedure ResetConfig(Config: TJObject);
var
  Options: IASPluginOptions;
begin
  Options := TASPluginOptions.Create;
  TASPluginOptions.SetDefaultValues(Options);
  SaveConfig(Options, Config);
end;

procedure TfmAureliusExport.actLoadConfigExecute(Sender: TObject);
var
  Config: TJObject;
  Strings: TStrings;
begin
  if HasFileOpened then
    if not CanCloseFile then
      Exit;

  if ConfigOpenDialog.Execute then
  begin
    Strings := TStringList.Create;
    try
      FConfigFileName := ConfigOpenDialog.FileName;
      Strings.LoadFromFile(FConfigFileName);
      Config := TJson.Deserialize<TJObject>(Strings.Text);
      try
        LoadConfig(FOptions, Config);
        LoadForm;
      finally
        Config.Free;
      end;
      Include(FConfigFileState, sfOpened);
      Exclude(FConfigFileState, sfModified);
      Self.Caption := GetFormCaption;
    finally
      Strings.Free;
    end;
  end;
end;

procedure TfmAureliusExport.actResetConfigExecute(Sender: TObject);
var
  DefaultConfig: TJObject;
begin
  DefaultConfig := TJObject.Create;
  try
    ResetConfig(DefaultConfig);
    LoadConfig(FOptions, DefaultConfig);
    LoadForm;
    if sfOpened in FConfigFileState then
    begin
      Include(FConfigFileState, sfModified);
      Self.Caption := GetFormCaption;
    end;
  finally
    DefaultConfig.Free;
  end;
end;

procedure TfmAureliusExport.actSaveConfigAsExecute(Sender: TObject);
begin
  if ConfigSaveDialog.Execute then
  begin
    FConfigFileName := ConfigSaveDialog.FileName;
    SaveConfigFile;
  end;
end;

procedure TfmAureliusExport.actSaveConfigExecute(Sender: TObject);
begin
  SaveConfigFile;
end;

procedure TfmAureliusExport.actSaveConfigUpdate(Sender: TObject);
begin
  TAction(Sender).Enabled := (sfOpened in FConfigFileState) and
                             (sfModified in FConfigFileState);
end;

procedure TfmAureliusExport.AdjustColumnWidths(Grid: TDBGrid);
var
  TotalColumnWidth, ColumnCount, GridClientWidth, Filler, i: Integer;
begin
  ColumnCount := Grid.Columns.Count;
  if ColumnCount = 0 then
    Exit;

  // compute total width used by grid columns and vertical lines if any
  TotalColumnWidth := 0;
  for i := 0 to ColumnCount-1 do
    TotalColumnWidth := TotalColumnWidth + Grid.Columns[i].Width;
  if dgColLines in Grid.Options then
    // include vertical lines in total (one per column)
    TotalColumnWidth := TotalColumnWidth + ColumnCount;

  // compute grid client width by excluding vertical scroll bar, grid indicator,
  // and grid border
  GridClientWidth := Grid.Width - GetSystemMetrics(SM_CXVSCROLL);
  if dgIndicator in Grid.Options then begin
    GridClientWidth := GridClientWidth - IndicatorWidth;
    if dgColLines in Grid.Options then
      Dec(GridClientWidth);
  end;
  if Grid.BorderStyle = bsSingle then begin
    if Grid.Ctl3D then // border is sunken (vertical border is 2 pixels wide)
      GridClientWidth := GridClientWidth - 4
    else // border is one-dimensional (vertical border is one pixel wide)
      GridClientWidth := GridClientWidth - 2;
  end;

  // adjust column widths
  if TotalColumnWidth < GridClientWidth then begin
    Filler := (GridClientWidth - TotalColumnWidth) div ColumnCount;
    for i := 0 to ColumnCount-1 do
      Grid.Columns[i].Width := Grid.Columns[i].Width + Filler;
  end
  else if TotalColumnWidth > GridClientWidth then begin
    Filler := (TotalColumnWidth - GridClientWidth) div ColumnCount;
    if (TotalColumnWidth - GridClientWidth) mod ColumnCount <> 0 then
      Inc(Filler);
    for i := 0 to ColumnCount-1 do
      Grid.Columns[i].Width := Grid.Columns[i].Width - Filler;
  end;
end;

procedure TfmAureliusExport.adsTablesAfterOpen(DataSet: TDataSet);
begin
  SetSelectedTablesText;
end;

procedure TfmAureliusExport.adsTablesAfterPost(DataSet: TDataSet);
begin
  SetSelectedTablesText;
end;

procedure TfmAureliusExport.btOkClick(Sender: TObject);
begin
  FSource := GenerateSource;
  ModalResult := mrOk;
end;

function TfmAureliusExport.CanCloseFile: Boolean;
begin
  Result := not (sfModified in FConfigFileState);
  if not Result then
  begin
    case Application.MessageBox(
      pchar(Format('Do you want to save changes to configuration file "%s"?', [ExtractFileName(FConfigFileName)])),
      pchar('Confirmation'), mb_IconQuestion + MB_YESNOCANCEL + MB_DEFBUTTON3)
    of
      mrYes: Result := actSaveConfig.Execute;
      mrNo: Result := True;
      mrCancel: Result := False;
    end;
  end;
end;

constructor TfmAureliusExport.Create(ADictionary: HDICTIONARY;
  ADatabase: IPluginDatabase; AOptions: IASPluginOptions);
begin
  inherited Create(nil);
  FDictionary := ADictionary;
  FDatabase := ADatabase;
  FOptions := AOptions;
  FTables := TObjectList<TTable>.Create;
  FSource := '';
  FConfigFileName := '';
  FConfigFileState := [];
end;

destructor TfmAureliusExport.Destroy;
begin
  FTables.Free;
  FDatabase := nil;
  inherited;
end;

procedure TfmAureliusExport.lbInvertSelectionClick(Sender: TObject);
begin
  InvertSelection;
end;

procedure TfmAureliusExport.lbSelectAllClick(Sender: TObject);
begin
  SelectAll;
end;

procedure TfmAureliusExport.lbSelectAllMouseEnter(Sender: TObject);
begin
  TLabel(Sender).Cursor := crHandPoint;
  TLabel(Sender).Font.Style := [fsUnderline];
  TLabel(Sender).Font.Color := clBlue;
end;

procedure TfmAureliusExport.lbSelectAllMouseLeave(Sender: TObject);
begin
  TLabel(Sender).Cursor := crDefault;
  TLabel(Sender).Font.Style := [];
  TLabel(Sender).Font.Color := clWindowText;
end;

procedure TfmAureliusExport.lbSelectNoneClick(Sender: TObject);
begin
  UnselectAll;
end;

procedure TfmAureliusExport.LoadExcludedTables;
var
  Bookmark: TArray<Byte>;
  TableConfig: ITableConfig;
begin
  Bookmark := adsTables.GetBookmark;
  adsTables.DisableControls;
  try
    adsTables.First;
    while not adsTables.Eof do
    begin
      TableConfig := FOptions.FindTableConfigByName(adsTablesName.AsString);
      if TableConfig = nil then
        TableConfig := FOptions.AddTableConfig(adsTablesName.AsString);
      adsTables.Edit;
      adsTablesChecked.Value := TableConfig.Checked;
      adsTables.Post;
      adsTables.Next;
    end;
  finally
    adsTables.Bookmark := Bookmark;
    adsTables.EnableControls;
  end;
end;

procedure TfmAureliusExport.LoadForm;
begin
  LoadExcludedTables;
  LoadProperties(tsAdvancedSettings);
end;

procedure TfmAureliusExport.FormClose(Sender: TObject;
  var Action: TCloseAction);
begin
  if HasFileOpened then
    if not CanCloseFile then
    begin
      Action := TCloseAction.caNone;
    end;
end;

procedure TfmAureliusExport.FormCreate(Sender: TObject);
var
  I: integer;
  PluginTable: IPluginTable;
begin
  FFormCaption := Self.Caption;
  Self.Constraints.MinHeight := Self.Height;
  Self.Constraints.MinWidth := Self.Width;

  for I := 0 to FDatabase.TableCount - 1 do
  begin
    PluginTable := FDatabase.GetTable(I);
    FTables.Add(TTable.Create(PluginTable.Name));
  end;

  adsTables.Close;
  adsTables.SetSourceList(FTables);
  adsTables.Open;

  AdjustColumnWidths(gridTables);
  LoadForm;
end;

procedure TfmAureliusExport.FormResize(Sender: TObject);
begin
  AdjustColumnWidths(gridTables);
end;

procedure TfmAureliusExport.FormShow(Sender: TObject);
begin
  Self.Caption := GetFormCaption;
  pageControlOptions.ActivePage := tsMapping;
  gridTables.SetFocus;
end;

function TfmAureliusExport.GenerateSource: string;
var
  SourceCode: WideString;
begin
  LoadExcludedTables;
  SourceCode := '';
  PluginCheck(GenerateSourceCode(FDictionary, FOptions, SourceCode));
  Result := SourceCode;
end;

function TfmAureliusExport.GetFormCaption: string;
begin
  if FConfigFileState = [] then
    Result := FFormCaption
  else if (sfOpened in FConfigFileState) then
  begin
    if not (sfModified in FConfigFileState) then
      Result := Format('%s - [%s]', [FFormCaption, ExtractFileName(FConfigFileName)])
    else
      Result := Format('%s - [%s*]', [FFormCaption, ExtractFileName(FConfigFileName)]);
  end;
end;

function TfmAureliusExport.GetSelectedTablesCount: integer;
var
  Table: TTable;
begin
  Result := 0;
  for Table in FTables do
    if Table.Checked then
      Inc(Result);
end;

procedure TfmAureliusExport.gridTablesCellClick(Column: TColumn);
var
  Pt: TPoint;
  Coord: TGridCoord;
  ClickCol: Integer;
begin
  Pt := Column.Grid.ScreenToClient(Mouse.CursorPos);
  Coord := Column.Grid.MouseCoord(Pt.X, Pt.Y);
  ClickCol := Coord.X;
  if (ClickCol = 0) then
    InvertSelectionOfCurrentRecord;
end;

procedure TfmAureliusExport.gridTablesDrawColumnCell(Sender: TObject;
  const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
const
   CtrlState: array[Boolean] of integer = (DFCS_BUTTONCHECK, DFCS_BUTTONCHECK or DFCS_CHECKED);
var
  R: TRect;
begin
  if (Column.Field.DataType = ftBoolean) then
  begin
    TDBGrid(Sender).Canvas.FillRect(Rect);
    R := Rect;
    InflateRect(R, -2, -2);
    if (VarIsNull(Column.Field.Value)) then
      DrawFrameControl(TDBGrid(Sender).Canvas.Handle, R, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_INACTIVE)
    else
      DrawFrameControl(TDBGrid(Sender).Canvas.Handle, R, DFC_BUTTON, CtrlState[Column.Field.AsBoolean]);
  end;
end;

procedure TfmAureliusExport.gridTablesKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if (key = VK_SPACE) then
    InvertSelectionOfCurrentRecord;
end;

function TfmAureliusExport.HasFileOpened: Boolean;
begin
  Result := sfOpened in FConfigFileState;
end;

procedure TfmAureliusExport.InvertSelection;
begin
  SelectTables(stInvert);
end;

procedure TfmAureliusExport.InvertSelectionOfCurrentRecord;
var
  TableConfig: ITableConfig;
begin
  adsTables.Edit;
  adsTablesChecked.Value:= not adsTablesChecked.AsBoolean;
  adsTables.Post;

  TableConfig := FOptions.FindTableConfigByName(adsTablesName.AsString);
  if TableConfig = nil then
    TableConfig := FOptions.AddTableConfig(adsTablesName.AsString);
  TableConfig.Checked := adsTablesChecked.Value;

  if sfOpened in FConfigFileState then
  begin
    Include(FConfigFileState, sfModified);
    Self.Caption := GetFormCaption;
  end;
end;

procedure TfmAureliusExport.LoadProperties(AContainer: TWinControl);
var
  c: Integer;
begin
  for c := 0 to AContainer.ControlCount - 1 do
  begin
    LoadProperty(AContainer.Controls[c]);
    if AContainer.Controls[c] is TWinControl then
      LoadProperties(TWinControl(AContainer.Controls[c]));
  end;
end;

procedure TfmAureliusExport.LoadProperty(Sender: TObject);
begin
  Inc(FLoading);
  try
    if Sender = edTableNameFormat then
      edTableNameFormat.Text := FOptions.TableNameFormat
    else
    if Sender = edFieldNameFormat then
      edFieldNameFormat.Text := FOptions.FieldNameFormat
    else
    if Sender = cbAssociationNameSource then
      cbAssociationNameSource.ItemIndex := FOptions.AssociationNameSource
    else
    if Sender = edAssociationNameFormat then
      edAssociationNameFormat.Text := FOptions.AssociationNameFormat
    else
    if Sender = cbManyValuedNameSource then
      cbManyValuedNameSource.ItemIndex := FOptions.ManyValuedNameSource
    else
    if Sender = edManyValuedNameFormat then
      edManyValuedNameFormat.Text := FOptions.ManyValuedNameFormat
    else
    if Sender = cbDefaultAssociationFetchMode then
      cbDefaultAssociationFetchMode.ItemIndex := FOptions.DefaultAssociationFetchMode
    else
    if Sender = cbDefaultAssociationCascade then
      cbDefaultAssociationCascade.ItemIndex := FOptions.DefaultAssociationCascadeDefinition
    else
    if Sender = cbDefaultManyValuedFetchMode then
      cbDefaultManyValuedFetchMode.ItemIndex := FOptions.DefaultManyValuedFetchMode
    else
    if Sender = cbDefaultOneToOneMapping then
      cbDefaultOneToOneMapping.ItemIndex := FOptions.DefaultOneToOneMapping
    else
    if Sender = cbCheckSequences then
      cbCheckSequences.ItemIndex := Ord(FOptions.CheckSequencesMode)
    else
    if Sender = edDefaultAncestorClass then
      edDefaultAncestorClass.Text := FOptions.DefaultAncestorClass
    else
    if Sender = edDefaultDynPropContainer then
      edDefaultDynPropContainer.Text := FOptions.DefaultDynPropContainer
    else
    if Sender = edDictionaryName then
      edDictionaryName.Text := FOptions.DictionaryName
    else
    if Sender = cbGenerateDictionary then
      cbGenerateDictionary.Checked := not FOptions.OmitDictionary
    else
    if Sender = cbRegisterEntities then
      cbRegisterEntities.Checked := FOptions.RegisterEntities
    else
    if Sender = cbNoNullable then
      cbNoNullable.Checked := FOptions.NoNullable
    else
    if Sender = cbTableNameSingularize then
      cbTableNameSingularize.Checked := FOptions.TableNameSingularize
    else
    if Sender = cbManyValuedNameSingularize then
      cbManyValuedNameSingularize.Checked := FOptions.ManyValuedNameSingularize
    else

    if Sender = cbTableNameCamelCase then
      cbTableNameCamelCase.Checked := FOptions.TableNameCamelCase
    else
    if Sender = cbTableNameRemoveUnderline then
      cbTableNameRemoveUnderline.Checked := FOptions.TableNameRemoveUnderline
    else

    if Sender = cbFieldNameCamelCase then
      cbFieldNameCamelCase.Checked := FOptions.FieldNameCamelCase
    else
    if Sender = cbFieldNameRemoveUnderline then
      cbFieldNameRemoveUnderline.Checked := FOptions.FieldNameRemoveUnderline
    else

    if Sender = cbAssociationNameCamelCase then
      cbAssociationNameCamelCase.Checked := FOptions.AssociationNameCamelCase
    else
    if Sender = cbAssociationNameRemoveUnderline then
      cbAssociationNameRemoveUnderline.Checked := FOptions.AssociationNameRemoveUnderline
    else

    if Sender = cbManyValuedNameCamelCase then
      cbManyValuedNameCamelCase.Checked := FOptions.ManyValuedNameCamelCase
    else
    if Sender = cbManyValuedNameRemoveUnderline then
      cbManyValuedNameRemoveUnderline.Checked := FOptions.ManyValuedNameRemoveUnderline
    else
  finally
    Dec(FLoading);
  end;
end;

procedure TfmAureliusExport.pageControlOptionsChange(Sender: TObject);
begin
  if pageControlOptions.ActivePage = tsPreview then
    UpdatePreview;
end;

procedure TfmAureliusExport.pageControlOptionsChanging(Sender: TObject;
  var AllowChange: Boolean);
begin
  AllowChange := FLoading = 0;
end;

procedure TfmAureliusExport.SaveConfigFile;
var
  Config: TJObject;
  Serializer: TJsonSerializer;
  Writer: TJsonWriter;
  Stream: TStringStream;
begin
  Config := TJObject.Create;
  try
    LoadExcludedTables;
    SaveConfig(FOptions, Config);
    Serializer := TJsonSerializer.Create;
    Stream := TStringStream.Create;
    Writer := TJsonWriter.Create(Stream);
    try
      Writer.IndentLength := 2;
      Serializer.Write(Config, Writer);
      Writer.Flush;
      Stream.Position := 0;
      Stream.SaveToFile(FConfigFileName);
    finally
      Writer.Free;
      Stream.Free;
      Serializer.Free;
    end;
    Include(FConfigFileState, sfOpened);
    Exclude(FConfigFileState, sfModified);
    Self.Caption := GetFormCaption;
  finally
    Config.Free;
  end;
end;

procedure TfmAureliusExport.SaveProperty(Sender: TObject);
begin
  if FLoading = 0 then
  begin
    if sfOpened in FConfigFileState then
    begin
      Include(FConfigFileState, sfModified);
      Self.Caption := GetFormCaption;
    end;

    if (Sender = edTableNameFormat) then
      FOptions.TableNameFormat := edTableNameFormat.Text
    else
    if (Sender = edFieldNameFormat) then
      FOptions.FieldNameFormat := edFieldNameFormat.Text
    else
    if (Sender = cbAssociationNameSource) then
      FOptions.AssociationNameSource := cbAssociationNameSource.ItemIndex
    else
    if (Sender = edAssociationNameFormat) then
      FOptions.AssociationNameFormat := edAssociationNameFormat.Text
    else
    if (Sender = cbManyValuedNameSource) then
      FOptions.ManyValuedNameSource := cbManyValuedNameSource.ItemIndex
    else
    if (Sender = edManyValuedNameFormat) then
      FOptions.ManyValuedNameFormat := edManyValuedNameFormat.Text
    else
    if (Sender = cbDefaultAssociationFetchMode) then
      FOptions.DefaultAssociationFetchMode := cbDefaultAssociationFetchMode.ItemIndex
    else
    if (Sender = cbDefaultAssociationCascade) then
      FOptions.DefaultAssociationCascadeDefinition := cbDefaultAssociationCascade.ItemIndex
    else
    if (Sender = cbDefaultManyValuedFetchMode) then
      FOptions.DefaultManyValuedFetchMode := cbDefaultManyValuedFetchMode.ItemIndex
    else
    if (Sender = cbDefaultOneToOneMapping) then
      FOptions.DefaultOneToOneMapping := cbDefaultOneToOneMapping.ItemIndex
    else
    if (Sender = cbCheckSequences) then
      FOptions.CheckSequencesMode := cbCheckSequences.ItemIndex
    else
    if (Sender = edDefaultAncestorClass) then
      FOptions.DefaultAncestorClass := edDefaultAncestorClass.Text
    else
    if (Sender = edDefaultDynPropContainer) then
      FOptions.DefaultDynPropContainer := edDefaultDynPropContainer.Text
    else
    if (Sender = edDictionaryName) then
      FOptions.DictionaryName := edDictionaryName.Text
    else
    if (Sender = cbGenerateDictionary) then
      FOptions.OmitDictionary := not cbGenerateDictionary.Checked
    else
    if (Sender = cbRegisterEntities) then
      FOptions.RegisterEntities := cbRegisterEntities.Checked
    else
    if (Sender = cbNoNullable) then
      FOptions.NoNullable := cbNoNullable.Checked
    else

    if (Sender = cbTableNameSingularize) then
      FOptions.TableNameSingularize := cbTableNameSingularize.Checked
    else
    if (Sender = cbManyValuedNameSingularize) then
      FOptions.ManyValuedNameSingularize := cbManyValuedNameSingularize.Checked
    else

    if (Sender = cbTableNameCamelCase) then
      FOptions.TableNameCamelCase := cbTableNameCamelCase.Checked
    else
    if (Sender = cbFieldNameCamelCase) then
      FOptions.FieldNameCamelCase := cbFieldNameCamelCase.Checked
    else
    if (Sender = cbAssociationNameCamelCase) then
      FOptions.AssociationNameCamelCase := cbAssociationNameCamelCase.Checked
    else
    if (Sender = cbManyValuedNameCamelCase) then
      FOptions.ManyValuedNameCamelCase := cbManyValuedNameCamelCase.Checked
    else

    if (Sender = cbTableNameRemoveUnderline) then
      FOptions.TableNameRemoveUnderline := cbTableNameRemoveUnderline.Checked
    else
    if (Sender = cbFieldNameRemoveUnderline) then
      FOptions.FieldNameRemoveUnderline := cbFieldNameRemoveUnderline.Checked
    else
    if (Sender = cbAssociationNameRemoveUnderline) then
      FOptions.AssociationNameRemoveUnderline := cbAssociationNameRemoveUnderline.Checked
    else
    if (Sender = cbManyValuedNameRemoveUnderline) then
      FOptions.ManyValuedNameRemoveUnderline := cbManyValuedNameRemoveUnderline.Checked
    else
  end;
end;

procedure TfmAureliusExport.SelectAll;
begin
  SelectTables(stAll);
end;

procedure TfmAureliusExport.SelectTables(Selection: TSelectionType);
var
  Bookmark: TArray<Byte>;
  TableConfig: ITableConfig;
begin
  Bookmark := adsTables.GetBookmark;
  adsTables.DisableControls;
  try
    adsTables.First;
    while not adsTables.Eof do
    begin
      adsTables.Edit;
      if Selection = stInvert then
        adsTablesChecked.Value := not adsTablesChecked.Value
      else
        adsTablesChecked.Value := Selection = stAll;
      adsTables.Post;

      TableConfig := FOptions.FindTableConfigByName(adsTablesName.AsString);
      if TableConfig = nil then
        TableConfig := FOptions.AddTableConfig(adsTablesName.AsString);
      TableConfig.Checked := adsTablesChecked.Value;

      adsTables.Next;
    end;
    if sfOpened in FConfigFileState then
    begin
      Include(FConfigFileState, sfModified);
      Self.Caption := GetFormCaption;
    end;
  finally
    adsTables.Bookmark := Bookmark;
    adsTables.EnableControls;
  end;
end;

procedure TfmAureliusExport.SetSelectedTablesText;
var
  Count: integer;
begin
  Count := GetSelectedTablesCount;
  if Count = 1 then
    lbSelectedTablesCount.Caption := '1 item selected'
  else
    lbSelectedTablesCount.Caption := Format('%d items selected', [Count]);
end;

procedure TfmAureliusExport.UnselectAll;
begin
  SelectTables(stNone);
end;

procedure TfmAureliusExport.UpdatePreview;
begin
  memoSource.Lines.Clear;
  memoSource.Lines.Text := GenerateSource;
  // Set cursor position :
  memoSource.SelStart := 0;
  memoSource.SelLength := 0;
  // Go to cursor (have it visible) :
  SendMessage(memoSource.Handle, EM_SCROLLCARET, 0, 0);
end;

{ TTable }

constructor TTable.Create(AName: string; AChecked: boolean);
begin
  FChecked := AChecked;
  FName := AName;
end;

end.
