////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2001-2026 Alexey Kuryakin daqgroup@mail.ru under MIT license //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// This file is part of the CRW-DAQ project by DaqGroup - component CRWLIB.   //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Purpose:                                                                   //
// Form Data Base Browser.                                                    //
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// History:                                                                   //
// 20231215 - Modified for FPC (A.K.)                                         //
////////////////////////////////////////////////////////////////////////////////

unit form_databasebrowser; // Form Data Base Browser

{$I _crw_sysdef.inc}

{$I _crw_sysmode.inc}

{$WARN 5023 off : Unit "$1" not used in $2}

interface

uses
 //////////////////////////////////////////////////////
 {$I _crw_uses_first.inc} // NB: MUST BE FIRST USES !!!
 //////////////////////////////////////////////////////
 sysutils, classes, strutils, math, db,
 Graphics, Controls, Forms, Dialogs, LMessages,
 ExtCtrls, ComCtrls, StdCtrls, Buttons, Menus,
 ActnList, ToolWin, ImgList, Clipbrd, Grids,
 lcltype, lclintf, Spin,
 _crw_alloc, _crw_fpu, _crw_rtc, _crw_fifo,
 _crw_str, _crw_eldraw, _crw_fio, _crw_plut,
 _crw_dynar, _crw_snd, _crw_guard, _crw_ef,
 _crw_zm, _crw_base64, _crw_crypt, _crw_sesman,
 _crw_dbcon, _crw_dbapi, _crw_sect, _crw_uri,
 _crw_proc,
 _crw_appforms, _crw_apptools, _crw_apputils;

type

  { TFormDatabaseBrowser }

  TFormDatabaseBrowser = class(TMasterForm)
    PanelDbContent: TPanel;
    PanelDbControl: TPanel;
    PanelDbcSqlInfo: TPanel;
    PanelDbInput: TPanel;
    PanelDbOutput: TPanel;
    PanelDbConnectionButtons: TPanel;
    PanelDbConnectionParams: TPanel;
    PanelDbInputButtons: TPanel;
    PanelDbInputParams: TPanel;
    PageControlDbConnection: TPageControl;
    TabSheetDbcBlobView: TTabSheet;
    TabSheetDbcSqlInfo: TTabSheet;
    TabSheetDbEngine: TTabSheet;
    LabelDbcEngine: TLabel;
    ComboBoxDbcEngine: TComboBox;
    LabelDbcProvider: TLabel;
    ComboBoxDbcProvider: TComboBox;
    LabelOdbcDriver: TLabel;
    ComboBoxOdbcDriver: TComboBox;
    PageControlDbcOutput: TPageControl;
    TabSheetDbcLog: TTabSheet;
    PanelDbcLogButtons: TPanel;
    MemoDbcLog: TMemo;
    TimerDbcMain: TTimer;
    TabSheetDbcSettings: TTabSheet;
    RadioGroupDbcClose: TRadioGroup;
    BitBtnDbcProviderDriverUpdate: TBitBtn;
    ImageListDbc16: TImageList;
    PanelDbcConnectionString: TPanel;
    ComboBoxDbcConnectionString: TComboBox;
    StatusBarDbcState: TStatusBar;
    LabelDbcConnectionString: TLabel;
    BitBtnDbcConnect: TBitBtn;
    ActionListDbc: TActionList;
    ActionDbcConnect: TAction;
    ActionDbcProviderDriverUpdate: TAction;
    BitBtnDbcDisconnect: TBitBtn;
    ActionDbcDisconnect: TAction;
    TabSheetDbcAccount: TTabSheet;
    ComboBoxDbcAuth: TComboBox;
    ComboBoxDbcUID: TComboBox;
    ComboBoxDbcSecr: TComboBox;
    EditDbcPWD: TEdit;
    ActionDbcPasswordHideShow: TAction;
    BitBtnDbcPasswordHideShow: TBitBtn;
    BitBtnDbcExecute: TBitBtn;
    ActionDbcExecute: TAction;
    PageControlSqlQuery: TPageControl;
    TabSheetDbcSqlQuery: TTabSheet;
    PanelSqlQueryButtons: TPanel;
    MemoDbcSqlQuery: TMemo;
    TabSheetDbcErrorsLog: TTabSheet;
    PanelDbcErrorsLogButtons: TPanel;
    MemoDbcErrorsLog: TMemo;
    GroupBoxDbcStatus: TGroupBox;
    LabelDbcBugsCount: TLabel;
    LabelDbcErrorsCount: TLabel;
    ActionDbcStatusReset: TAction;
    BitBtnDbcStatusReset: TBitBtn;
    TabSheetSqlResult: TTabSheet;
    PanelSqlResultButtons: TPanel;
    StringGridDbcSqlResult: TStringGrid;
    LabelDbcEmptyDataMarker: TLabel;
    ComboBoxDbcEmptyDataMarker: TComboBox;
    ActionDbcSqlQuerySelectAll: TAction;
    BitBtnDbcSqlQuerySelectAll: TBitBtn;
    ActionDbcSqlQueryUnselect: TAction;
    BitBtnDbcSqlQueryUnselect: TBitBtn;
    ActionDbcSqlQueryClear: TAction;
    BitBtnDbcSqlQueryClear: TBitBtn;
    ActionDbcSqlQueryCut: TAction;
    BitBtnDbcSqlQueryCut: TBitBtn;
    ActionDbcSqlQueryCopy: TAction;
    BitBtnDbcSqlQueryCopy: TBitBtn;
    ActionDbcSqlQueryPaste: TAction;
    BitBtnDbcSqlQueryPaste: TBitBtn;
    TabSheetDbcSqlQuerySamples: TTabSheet;
    PanelDbcSqlQuerySamplesButtons: TPanel;
    MemoDbcSqlQuerySamples: TMemo;
    TabSheetDbcLocation: TTabSheet;
    CheckBoxDbcAuth: TCheckBox;
    CheckBoxDbcSecr: TCheckBox;
    CheckBoxDbcServer: TCheckBox;
    ComboBoxDbcServAl: TComboBox;
    ComboBoxDbcServer: TComboBox;
    CheckBoxDbcSource: TCheckBox;
    ComboBoxDbcDataAl: TComboBox;
    ComboBoxDbcDatabase: TComboBox;
    ActionDbcFindDatabase: TAction;
    BitBtnFindDatabase: TBitBtn;
    OpenDialogDbcDatabase: TOpenDialog;
    CheckBoxDbcMergeLocation: TCheckBox;
    ActionDbcSqlQuerySamplesSelectAll: TAction;
    BitBtnDbcSqlQuerySamplesSelectAll: TBitBtn;
    ActionDbcSqlQuerySamplesUnselect: TAction;
    BitBtnDbcSqlQuerySamplesUnselect: TBitBtn;
    ActionDbcSqlQuerySamplesCopy: TAction;
    BitBtnDbcSqlQuerySamplesCopy: TBitBtn;
    ActionDbcLogSelectAll: TAction;
    BitBtnDbcLogSelectAll: TBitBtn;
    ActionDbcLogUnselect: TAction;
    ActionDbcLogClear: TAction;
    BitBtnDbcLogUnselect: TBitBtn;
    BitBtnDbcLogClear: TBitBtn;
    ActionDbcLogCopy: TAction;
    BitBtnDbcLogCopy: TBitBtn;
    ActionDbcErrorsLogSelectAll: TAction;
    BitBtnDbcErrorsLogSelectAll: TBitBtn;
    ActionDbcErrorsLogUnselect: TAction;
    BitBtnDbcErrorsLogUnselect: TBitBtn;
    ActionDbcErrorsLogClear: TAction;
    BitBtnDbcErrorsLogClear: TBitBtn;
    ActionDbcErrorsLogCopy: TAction;
    BitBtnDbcErrorsLogCopy: TBitBtn;
    ActionDbcSqlResultClear: TAction;
    LabelDbcMaxRecords: TLabel;
    ComboBoxDbcMaxRecords: TComboBox;
    ActionDatabaseBrowserManual: TAction;
    ActionDbcSqlResultSelectAll: TAction;
    BitBtnDbcSqlResultSelectAll: TBitBtn;
    ActionDbcSqlResultUnselect: TAction;
    BitBtnDbcSqlResultUnselect: TBitBtn;
    ActionDbcSqlResultCopy: TAction;
    BitBtnDbcSqlResultCopy: TBitBtn;
    GroupBoxDbcSqlResultCopy: TGroupBox;
    CheckBoxDbcSqlResultHeadTypes: TCheckBox;
    CheckBoxDbcSqlResultHeadNames: TCheckBox;
    ComboBoxDbcReadTables: TComboBox;
    ActionDbcReadTables: TAction;
    BitBtnDatabaseBrowserManual: TBitBtn;
    BitBtnDbcReadTables: TBitBtn;
    ActionDbcSqlResultNiceView: TAction;
    BitBtnDbcSqlResultNiceView: TBitBtn;
    BitBtnDbcSqlResultClear: TBitBtn;
    LabelDbcSqlResultMinWidth: TLabel;
    SpinEditDbcSqlResultMinWidth: TSpinEdit;
    LabelDbcSqlResultMaxWidth: TLabel;
    SpinEditDbcSqlResultMaxWidth: TSpinEdit;
    GroupBoxDbcPwdCrypt: TGroupBox;
    LabelDbcPwdCryptMode: TLabel;
    ComboBoxDbcPwdCryptMode: TComboBox;
    ActionDbcPwdCryptCopy: TAction;
    BitBtnDbcPwdCryptCopy: TBitBtn;
    EditDbcPwdCryptResult: TEdit;
    ActionDbcMenuTables: TAction;
    BitBtnDbcMenuTables: TBitBtn;
    GroupBoxDbcOtherOpt: TGroupBox;
    CheckBoxDbcSortListOfTables: TCheckBox;
    TabSheetDbcDateTimeFormat: TTabSheet;
    CheckBoxDbcFormatDate: TCheckBox;
    ComboBoxDbcFormatDate: TComboBox;
    CheckBoxDbcFormatTime: TCheckBox;
    ComboBoxDbcFormatTime: TComboBox;
    CheckBoxDbcFormatTimeStamp: TCheckBox;
    ComboBoxDbcFormatTimeStamp: TComboBox;
    CheckBoxDbcShowDateAsReal: TCheckBox;
    CheckBoxDbcShowTimeAsReal: TCheckBox;
    CheckBoxDbcShowTimeStampAsReal: TCheckBox;
    CheckBoxDbcUseMsSinceXmas: TCheckBox;
    CheckBoxDbcCompactTimeStamp: TCheckBox;
    ActionDbcSqlResultSaveAs: TAction;
    SaveDialogDbcSqlResults: TSaveDialog;
    BitBtnDbcSqlResultsSaveAs: TBitBtn;
    BitBtnOdbcAd32: TBitBtn;
    ButtonDbcSqlInfoSqlDb: TButton;
    ButtonDbcSqlInfoConnBrief: TButton;
    ButtonDbcSqlInfoConnProps: TButton;
    ActionDbcSqlQueryTableSave: TAction;
    BitBtnDbcSqlQueryTableSave: TBitBtn;
    SaveDialogDbcSqlQueryTableSave: TSaveDialog;
    PanelDbcBlobView: TPanel;
    LabelDbcBlobViewMode: TLabel;
    ComboBoxDbcBlobViewMode: TComboBox;
    ActionDbcSqlResultBlobView: TAction;
    BitBtnDbcSqlResultBlobView: TBitBtn;
    CheckBoxDbcBlobViewDump: TCheckBox;
    LabelDbcBlobViewDump: TLabel;
    CheckBoxConnectionURI: TCheckBox;
    EditDbcConnectionURI: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormHide(Sender: TObject);
    procedure TimerDbcMainTimer(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ActionDbcConnectExecute(Sender: TObject);
    procedure ActionDbcProviderDriverUpdateExecute(Sender: TObject);
    procedure ActionDbcDisconnectExecute(Sender: TObject);
    procedure ActionDbcPasswordHideShowExecute(Sender: TObject);
    procedure ActionDbcExecuteExecute(Sender: TObject);
    procedure ActionDbcStatusResetExecute(Sender: TObject);
    procedure ActionDbcSqlQuerySelectAllExecute(Sender: TObject);
    procedure ActionDbcSqlQueryUnselectExecute(Sender: TObject);
    procedure ActionDbcSqlQueryClearExecute(Sender: TObject);
    procedure ActionDbcSqlQueryCutExecute(Sender: TObject);
    procedure ActionDbcSqlQueryCopyExecute(Sender: TObject);
    procedure ActionDbcSqlQueryPasteExecute(Sender: TObject);
    procedure ActionDbcSqlQueryTableSaveExecute(Sender: TObject);
    procedure SaveDialogDbcSqlQueryTableSaveTypeChange(Sender: TObject);
    procedure ComboBoxDbcConnectionStringChange(Sender: TObject);
    procedure ComboBoxDbcProviderChange(Sender: TObject);
    procedure ComboBoxOdbcDriverChange(Sender: TObject);
    procedure ComboBoxDbcUIDChange(Sender: TObject);
    procedure EditDbcPWDChange(Sender: TObject);
    procedure ComboBoxDbcPwdCryptModeChange(Sender: TObject);
    procedure ActionDbcFindDatabaseExecute(Sender: TObject);
    procedure ComboBoxDbcServerChange(Sender: TObject);
    procedure CheckBoxDbcMergeLocationClick(Sender: TObject);
    procedure ComboBoxDbcDatabaseChange(Sender: TObject);
    procedure ActionDbcSqlQuerySamplesSelectAllExecute(Sender: TObject);
    procedure ActionDbcSqlQuerySamplesUnselectExecute(Sender: TObject);
    procedure ActionDbcSqlQuerySamplesCopyExecute(Sender: TObject);
    procedure ActionDbcLogSelectAllExecute(Sender: TObject);
    procedure ActionDbcLogUnselectExecute(Sender: TObject);
    procedure ActionDbcLogClearExecute(Sender: TObject);
    procedure ActionDbcLogCopyExecute(Sender: TObject);
    procedure ActionDbcErrorsLogSelectAllExecute(Sender: TObject);
    procedure ActionDbcErrorsLogUnselectExecute(Sender: TObject);
    procedure ActionDbcErrorsLogClearExecute(Sender: TObject);
    procedure ActionDbcErrorsLogCopyExecute(Sender: TObject);
    procedure ActionDbcSqlResultClearExecute(Sender: TObject);
    procedure ActionDatabaseBrowserManualExecute(Sender: TObject);
    procedure ActionDbcSqlResultSelectAllExecute(Sender: TObject);
    procedure ActionDbcSqlResultUnselectExecute(Sender: TObject);
    procedure ActionDbcSqlResultCopyExecute(Sender: TObject);
    procedure ActionDbcReadTablesExecute(Sender: TObject);
    procedure ComboBoxDbcReadTablesChange(Sender: TObject);
    procedure ActionDbcSqlResultNiceViewExecute(Sender: TObject);
    procedure ActionDbcPwdCryptCopyExecute(Sender: TObject);
    procedure ActionDbcMenuTablesExecute(Sender: TObject);
    procedure ActionDbcSqlResultSaveAsExecute(Sender: TObject);
    procedure ComboBoxDbcEngineChange(Sender: TObject);
    procedure BitBtnOdbcAd32Click(Sender: TObject);
    procedure ButtonDbcSqlInfoSqlDbClick(Sender: TObject);
    procedure ButtonDbcSqlInfoConnBriefClick(Sender: TObject);
    procedure ButtonDbcSqlInfoConnPropsClick(Sender: TObject);
    procedure ActionDbcSqlResultBlobViewExecute(Sender: TObject);
  private
    { Private declarations }
    dbc        : Integer;
    dbcBugs    : Integer;
    dbcErrors  : Integer;
    lastBugs   : Integer;
    lastErrors : Integer;
    MaxRecords : Integer;
    procedure DbcFree;
    procedure DbcUpdateState;
    procedure DbcHandleErrors;
    procedure AdjustParameters;
    function  ConnectionStringToURI(cs:LongString):LongString;
    procedure UpdateConnectionURI;
    procedure DbcUpdateEngines;
    procedure DbcUpdateServers;
    procedure DbcSqlResultClear;
    procedure DbcSqlResultCells(Col,Row:Integer; Data:LongString; Expand:Boolean=false);
    function  db_query_dbapimasterkey:LongString;
    procedure DbcUpdatePwdCryptResult;
    procedure DbcInvalidateErrorsBugs;
    procedure DbcConnectionStringSamplesRead;
    procedure DbcDatabaseBrowserSamplesRead;
    procedure DbcSqlQuerySamplesRead;
    procedure DbcHandleProviderChange;
    procedure ValidateLocationFileType;
    procedure DbcHandleAccountChange;
    function  DbcAccountApply(cs:LongString):LongString;
    function  DbcUriApply(cs:LongString):LongString;
    procedure DbcHandleLocationChange;
    procedure UpdateProviders(Renew:Boolean=false);
    procedure UpdateOdbcDrivers(Renew:Boolean=false);
    function  EmptyDataMarker:LongString;
    function  DbcMaxRecords:LongString;
    procedure DbcApplyMaxRecords;
    procedure RunActionExternal(Action:TAction);
    function  DbcSqlResultSelText(const R:TGridRect; FieldSeparator:Char=#9):LongString;
    function  DbcSqlQuerySelectAllTablesRead:LongString;
    procedure DbcReadTablesChanged;
    procedure DbcReadTablesClear;
    procedure AdaptDbcDatabaseName;
  public
    { Public declarations }
    function  DbcEngineId:Integer;
    function  DbcSqlQuery:LongString;
    function  DbcEngineName:LongString;
    function  DbcConnectionString:LongString;
    procedure DbcLog(When:TDateTime; What:LongString);
    procedure DbcLogFail(When:TDateTime; What:LongString);
    procedure DbcErrorsLog(When:TDateTime; What:LongString);
    function  DbcTimePrompt(When:TDateTime; cPad:Char=' '):LongString;
    function  DbcPad(cPad:Char=' '):LongString;
    function  FormatTimeStamp(tm:Double; typ:Integer):LongString;
    function  FormatDate(tm:Double; typ:Integer):LongString;
    function  FormatTime(tm:Double; typ:Integer):LongString;
    procedure DbcSqlResultSaveAs(aFileName:LongString);
    function  DatabaseBrowserDefaultProvider(aEngine:Integer):LongString;
  end;

var
 FormDatabaseBrowser:TFormDatabaseBrowser=nil;

const
  DatabaseBrowserFreeOnClose:Boolean=false;

const
 DatabaseBrowserDefaultOdbcDriver:LongString='SQLite3';

procedure OpenDatabaseBrowser;
procedure ExecuteDatabaseBrowser;

implementation

{$R *.lfm}

uses
 _crw_dblaz,
 Form_ListBoxSelection;

procedure OpenDatabaseBrowser;
begin
 try
  if not Assigned(FormDatabaseBrowser) then begin
   Application.CreateForm(TFormDatabaseBrowser, FormDatabaseBrowser);
   FormDatabaseBrowser.Master:=@FormDatabaseBrowser;
  end;
  if Assigned(FormDatabaseBrowser) then begin
   if not FormDatabaseBrowser.Visible then FormDatabaseBrowser.Show;
   FormDatabaseBrowser.WindowState:=wsNormal;
   FormDatabaseBrowser.BringToFront;
  end;
 except
  on E:Exception do BugReport(E,Application,'OpenDatabaseBrowser');
 end;
end;

procedure ExecuteDatabaseBrowser;
begin
 if CanShowModal(FormDatabaseBrowser) then
 try
  if not Assigned(FormDatabaseBrowser) then begin
   Application.CreateForm(TFormDatabaseBrowser, FormDatabaseBrowser);
   FormDatabaseBrowser.Master:=@FormDatabaseBrowser;
  end;
  if Assigned(FormDatabaseBrowser) then begin
   mrVoice(FormDatabaseBrowser.ShowModal);
  end;
 except
  on E:Exception do BugReport(E,Application,'ExecuteDatabaseBrowser');
 end;
end;

 ///////////////////
 // Utility routines
 ///////////////////

procedure MemoClear(Memo:TMemo);
begin
 if (Memo=nil) then Exit;
 try
  if (Memo.SelLength>0) and not Memo.ReadOnly
  then Memo.ClearSelection
  else Memo.Clear;
 except
  on E:Exception do BugReport(E,FormDatabaseBrowser,'MemoClear');
 end;
end;

procedure MemoCopyToClipboard(Memo:TMemo);
begin
 if (Memo=nil) then Exit;
 try
  Memo.CopyToClipboard;
 except
  on E:Exception do BugReport(E,FormDatabaseBrowser,'MemoCopyToClipboard');
 end;
end;

procedure MemoCutToClipboard(Memo:TMemo);
begin
 if (Memo=nil) then Exit;
 try
  Memo.CutToClipboard;
 except
  on E:Exception do BugReport(E,FormDatabaseBrowser,'MemoCutToClipboard');
 end;
end;

procedure MemoPasteFromClipboard(Memo:TMemo);
begin
 if (Memo=nil) then Exit;
 try
  Memo.PasteFromClipboard;
 except
  on E:Exception do BugReport(E,FormDatabaseBrowser,'MemoPasteFromClipboard');
 end;
end;

procedure StringsAdd(Strings:TStrings; s:LongString; Check:Boolean=true);
begin
 if (Strings=nil) then Exit;
 if Check and (Strings.IndexOf(s)>=0) then Exit;
 Strings.Add(s);
end;

 //////////////////////////////////////
 // TFormDatabaseBrowser implementation
 //////////////////////////////////////

procedure TFormDatabaseBrowser.FormCreate(Sender: TObject);
begin
 inherited;
 SetStandardFont(Self);
 SetAllButtonsCursor(Self,crHandPoint);
 DbcLog(Now,'Welcome to Database Browser.');
 DbcErrorsLog(Now,'List of provider detected errors:');
 DbcInvalidateErrorsBugs;
 AdjustParameters;
 MaxRecords:=0;
end;

procedure TFormDatabaseBrowser.FormDestroy(Sender: TObject);
begin
 DatabaseBrowserFreeOnClose:=(RadioGroupDbcClose.ItemIndex>0);
 DbcFree;
 inherited;
end;

procedure TFormDatabaseBrowser.FormClose(Sender: TObject; var Action: TCloseAction);
begin
 if (RadioGroupDbcClose.ItemIndex>0)
 then Action:=caFree
 else Action:=caHide;
end;

procedure TFormDatabaseBrowser.FormHide(Sender: TObject);
begin
 inherited;
 TimerDbcMain.Enabled:=false;
 ActionDbcDisconnect.Execute;
end;

procedure TFormDatabaseBrowser.FormShow(Sender: TObject);
begin
 inherited;
 DbcInvalidateErrorsBugs;
 TimerDbcMain.Enabled:=true;
end;

procedure TFormDatabaseBrowser.AdjustParameters;
begin
 try
  RadioGroupDbcClose.ItemIndex:=Ord(DatabaseBrowserFreeOnClose);
  if (ComboBoxDbcPwdCryptMode.Items.Count>6) then ComboBoxDbcPwdCryptMode.ItemIndex:=6; // RC6
  { Fonts }
  RestoreFont(CheckBoxDbcMergeLocation.Font,DefaultSansFont);
  RestoreFont(BitBtnDbcStatusReset.Font,DefaultSansNarrowFont);
  RestoreFont(CheckBoxDbcSqlResultHeadTypes.Font,DefaultSansFont);
  RestoreFont(CheckBoxDbcSqlResultHeadNames.Font,DefaultSansFont);
  RestoreFont(ComboBoxDbcReadTables.Font,DefaultSansFont);
  RestoreFont(BitBtnDbcReadTables.Font,DefaultSansFont);
  RestoreFont(CheckBoxDbcSortListOfTables.Font,DefaultSansFont);
  ComboBoxDbcConnectionString.Font.Bold:=True;
  LabelDbcConnectionString.Font.Bold:=True;
  LabelDbcPwdCryptMode.Font.Bold:=True;
  {}
  ComboBoxDbcAuth.Items.Text:=StringReplace(TDbEngineAssistant.sia_UserName,';',EOL,[rfReplaceAll]);
  ComboBoxDbcSecr.Items.Text:=StringReplace(TDbEngineAssistant.sia_Password,';',EOL,[rfReplaceAll]);
  ComboBoxDbcServAl.Items.Text:=StringReplace(TDbEngineAssistant.sia_HostName,';',EOL,[rfReplaceAll]);
  ComboBoxDbcDataAl.Items.Text:=StringReplace(TDbEngineAssistant.sia_DataBase,';',EOL,[rfReplaceAll]);
  if (ComboBoxDbcAuth.Items.Count>0) then ComboBoxDbcAuth.ItemIndex:=0;
  if (ComboBoxDbcSecr.Items.Count>0) then ComboBoxDbcSecr.ItemIndex:=0;
  if (ComboBoxDbcServAl.Items.Count>0) then ComboBoxDbcServAl.ItemIndex:=0;
  if (ComboBoxDbcDataAl.Items.Count>0) then ComboBoxDbcDataAl.ItemIndex:=0;
  { Engine List }
  DbcUpdateEngines;
  { Server list }
  DbcUpdateServers;
  { Providers}
  UpdateProviders;
  UpdateOdbcDrivers;
  { Samples }
  DbcSqlQuerySamplesRead;
  DbcConnectionStringSamplesRead;
  DbcDatabaseBrowserSamplesRead;
  DbcHandleProviderChange;
  ValidateLocationFileType;
  DbcHandleLocationChange;
  DbcHandleAccountChange;
  { Pages }
  PageControlSqlQuery.ActivePageIndex:=0;
  PageControlDbcOutput.ActivePageIndex:=0;
  PageControlDbConnection.ActivePageIndex:=0;
  { SqlResult }
  DbcSqlResultClear;
  { SaveDialogDbcSqlQueryTableSave }
  SaveDialogDbcSqlQueryTableSave.InitialDir:=SessionManager.VarTmpDir;
  SaveDialogDbcSqlQueryTableSave.FileName:=SessionManager.VarTmpFile('table.csv');
 except
  on E:Exception do BugReport(E,Self,'AdjustParameters');
 end;
end;

procedure ValidateUriTerm(var s:LongString; sep:TCharSet);
begin
 if HasChars(s,sep) then s:=percent_encode(s,sep);
end;

function TFormDatabaseBrowser.ConnectionStringToURI(cs:LongString):LongString;
const dbsep=JustSpaces+['%','?','&','#',';',',']; ausep=dbsep+['/',':'];
var uri,pv,au,us,pa,ho,po,db,qu,pm:LongString;
const sep=';'; pmsep=JustSpaces+['%','&','#'];
begin
 Result:=''; cs:=Trim(cs); if (cs='') then Exit;
 uri:='';pv:='';au:='';us:='';pa:='';ho:='';po:='';db:='';qu:='';
 pv:=db_query_connectionString(cs,TDbEngineAssistant.sia_Provider);
 us:=db_query_connectionString(cs,TDbEngineAssistant.sia_UserName);
 pa:=db_query_connectionString(cs,TDbEngineAssistant.sia_Password);
 ho:=db_query_connectionString(cs,TDbEngineAssistant.sia_HostName);
 db:=db_query_connectionString(cs,TDbEngineAssistant.sia_DataBase);
 po:=db_query_connectionString(cs,TDbEngineAssistant.sia_Port);
 pm:=db_drop_connectionString(cs,TDbEngineAssistant.sia_Provider+sep
                                +TDbEngineAssistant.sia_UserName+sep
                                +TDbEngineAssistant.sia_Password+sep
                                +TDbEngineAssistant.sia_HostName+sep
                                +TDbEngineAssistant.sia_DataBase+sep
                                +TDbEngineAssistant.sia_Port);
 ValidateUriTerm(us,ausep); ValidateUriTerm(pa,ausep);
 ValidateUriTerm(ho,ausep); ValidateUriTerm(po,ausep);
 ValidateUriTerm(db,dbsep); ValidateUriTerm(pm,pmsep);
 qu:=StringReplace(pm,';','&',[rfReplaceAll]);
 if (us<>'') then au:=IfThen(pa='',au+us,au+us+':'+pa)+'@';
 if (ho<>'') then au:=IfThen(po='',au+ho,au+ho+':'+po);
 if (db<>'') then db:='/'+db;
 if (qu<>'') then qu:='?'+qu;
 if (pv<>'') then uri:=pv+'://'+au+db+qu;
 Result:=uri;
end;

procedure TFormDatabaseBrowser.UpdateConnectionURI;
begin
 EditDbcConnectionURI.Text:=ConnectionStringToURI(DbcConnectionString);
end;

procedure TFormDatabaseBrowser.ComboBoxDbcConnectionStringChange(Sender: TObject);
begin
 UpdateConnectionURI;
end;

function TFormDatabaseBrowser.DatabaseBrowserDefaultProvider(aEngine:Integer):LongString;
begin
 Result:='';
 case aEngine of
  db_engine_ado: Result:=db_provider_msdasql;
  db_engine_sqldb: Result:='SQLite3';
  db_engine_zeos:  Result:='sqlite';
 end;
end;

procedure TFormDatabaseBrowser.DbcUpdateEngines;
var i,def:Integer;
begin
 try
  def:=0;
  if not ReadIniFileInteger(SysIniFile,'[DbApi.Defaults]','EngineId',def)
  then def:=0; if (def<=0) then def:=db_engine_default;
  ComboBoxDbcEngine.Items.Text:='Default';
  for i:=1 to db_engine_count do begin
   ComboBoxDbcEngine.Items.Add(db_engine_name(i));
   if (i=def) then ComboBoxDbcEngine.ItemIndex:=i;
  end;
  AdaptDbcDatabaseName;
 except
  on E:Exception do BugReport(E,Self,'DbcUpdateEngines');
 end;
end;

procedure TFormDatabaseBrowser.AdaptDbcDatabaseName;
begin
 ComboBoxDbcDatabase.Text:=AdaptFileName(ComboBoxDbcDatabase.Text);
end;

procedure TFormDatabaseBrowser.DbcUpdateServers;
var hosts,host:LongString; i:Integer;
begin
 try
  if (ComboBoxDbcServer.Items.Count<=1) then begin
   StringsAdd(ComboBoxDbcServer.Items,'localhost');
   StringsAdd(ComboBoxDbcServer.Items,AnsiLowerCase(ComputerName));
   StringsAdd(ComboBoxDbcServer.Items,HostName(0));
   hosts:=GetHostList;
   for i:=1 to WordCount(hosts,ScanSpaces) do begin
    host:=ExtractWord(i,hosts,ScanSpaces);
    StringsAdd(ComboBoxDbcServer.Items,host);
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcUpdateServers');
 end;
end;

procedure TFormDatabaseBrowser.UpdateProviders(Renew:Boolean=false);
var s:LongString; i:Integer;
begin
 try
  s:=ComboBoxDbcProvider.Text;
  if Renew then OleDbProviderNames.Text:='';
  if (s='') then s:=DatabaseBrowserDefaultProvider(DbcEngineId);
  ComboBoxDbcProvider.Items.Text:=KnownDbApiProviderNames(DbcEngineId);
  i:=ComboBoxDbcProvider.Items.IndexOf(s);
  if (i>=0) then ComboBoxDbcProvider.ItemIndex:=i;
 except
  on E:Exception do BugReport(E,Self,'UpdateProviders');
 end;
end;

procedure TFormDatabaseBrowser.UpdateOdbcDrivers(Renew:Boolean=false);
var s:LongString; i:Integer;
begin
 try
  s:=ComboBoxOdbcDriver.Text;
  if Renew then OdbcDriverNames.Text:='';
  if (s='') then s:=DatabaseBrowserDefaultOdbcDriver;
  ComboBoxOdbcDriver.Items.Text:=OdbcDriverNames.Text;
  i:=ComboBoxOdbcDriver.Items.IndexOf(s);
  if (DbcEngineId in [db_engine_sqldb,db_engine_zeos]) then begin
   if (PosI('odbc',ComboBoxDbcProvider.Text)=0) then i:=-1;
  end;
  if (i>=0) then ComboBoxOdbcDriver.ItemIndex:=i else ComboBoxOdbcDriver.ItemIndex:=-1;
 except
  on E:Exception do BugReport(E,Self,'UpdateOdbcDrivers');
 end;
end;

procedure TFormDatabaseBrowser.DbcConnectionStringSamplesRead;
var Samples:TText; i,p:Integer; Sect,Line:LongString;
begin
 try
  Sect:=Format('[DbApi.ConnectionString.Samples.%s]',[DbcEngineName]);
  Samples:=ExtractListSection(SysIniFile,Sect,efAsIs);
  try
   for i:=Samples.Count-1 downto 0 do begin
    Line:=Samples[i]; p:=Pos('=',Line);
    if (p=0) or not SameText(ExtractWord(1,Line,ScanSpaces),'ConnectionString') then begin
     Samples.DelLn(i);
     Continue;
    end;
    Line:=Trim(Copy(Line,p+1,Length(Line)-p));
    Samples[i]:=Trim(Line);
   end;
   p:=-1;
   if (ComboBoxOdbcDriver.Text<>'') then
   for i:=0 to Samples.Count-1 do begin
    if (Pos(AnsiUpperCase(ComboBoxOdbcDriver.Text),AnsiUpperCase(Samples[i]))>0)
    then p:=i; if (p>=0) then Break;
   end;
   ComboBoxDbcConnectionString.Items.Text:=Samples.Text;
   if (p>=0) then ComboBoxDbcConnectionString.ItemIndex:=p;
  finally
   Kill(Samples);
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcConnectionStringSamplesRead');
 end;
end;

procedure PackUniqStrings(Lines:TStrings);
var i:Integer; S:LongString;
begin
 S:='';
 for i:=Lines.Count-1 downto 0 do begin
  if (Lines[i]<>'') then begin
   if SameText(Lines[i],S)
   then Lines.Delete(i);
  end;
  S:=Lines[i];
 end;
end;

procedure TFormDatabaseBrowser.DbcDatabaseBrowserSamplesRead;
var Samples:TText; i:Integer; Line,Item:LongString;
begin
 try
  Samples:=ExtractListSection(SysIniFile,'[DbApi.DatabaseBrowser.Samples]',efConfigNC);
  try
   for i:=Samples.Count-1 downto 0 do begin
    Line:=Trim(ExpEnv(Samples[i]));
    Line:=AdaptFileName(Line);
    if (ExtractFileDir(Line)<>'')
    then Line:=UnifyFileAlias(Line);
    if IsEmptyStr(Line) or ((ExtractFileDir(Line)<>'') and not FileExists(Line)) then begin
     Samples.DelLn(i);
     Continue;
    end;
    Samples[i]:=Trim(Line);
   end;
   if (Samples.Count>0) then begin
    Item:=ComboBoxDbcDatabase.Text;
    ComboBoxDbcDatabase.Items.Text:=SortTextLines(Samples.Text);
    PackUniqStrings(ComboBoxDbcDatabase.Items);
    ComboBoxDbcDatabase.Text:=Item;
   end;
  finally
   Kill(Samples);
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcDatabaseBrowserSamplesRead');
 end;
end;

procedure TFormDatabaseBrowser.DbcSqlQuerySamplesRead;
var Samples:TText; i:Integer; Line:LongString;
begin
 try
  Samples:=ExtractListSection(SysIniFile,'[DbApi.SQL.Query.Samples]',efAsIs);
  try
   for i:=Samples.Count-1 downto 0 do begin
    Line:=SysUtils.Trim(Samples[i]);
    if IsEmptyStr(Line) or (Pos(';',Line)=1) then begin
     Samples.DelLn(i);
     Continue;
    end;
    Samples[i]:=Line;
   end;
   MemoDbcSqlQuerySamples.Text:=Samples.Text;
  finally
   Kill(Samples);
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcSqlQuerySamplesRead');
 end;
end;

procedure TFormDatabaseBrowser.DbcHandleProviderChange;
var Items:TStrings; i:Integer; Line,Prov,Driv:LongString;
begin
 try
  DbcConnectionStringSamplesRead;
  Prov:=AnsiUpperCase(SysUtils.Trim(ComboBoxDbcProvider.Text));
  Driv:=AnsiUpperCase(SysUtils.Trim(ComboBoxOdbcDriver.Text));
  Items:=ComboBoxDbcConnectionString.Items;
  for i:=0 to Items.Count-1 do begin
   Line:=AnsiUpperCase(SysUtils.Trim(Items.Strings[i]));
   Line:=StringReplace(Line,' ;',';',[rfReplaceAll])+';';
   if (Pos(Prov+';',Line)>0) and (Pos(Driv+';',Line)>0) then begin
    ComboBoxDbcConnectionString.ItemIndex:=i;
    Break;
   end;
  end;
  if (DbcEngineId in [db_engine_sqldb,db_engine_zeos]) then
  if (WordIndex(Prov,'SQLite,SQLite3,IB,Firebird,Interbase',ScanSpaces)=0) then begin
   if IsNonEmptyStr(ExtractFileDir(ComboBoxDbcDatabase.Text))
   then ComboBoxDbcDatabase.Text:=ExtractBaseName(ComboBoxDbcDatabase.Text);
  end;
  UpdateConnectionURI;
 except
  on E:Exception do BugReport(E,Self,'DbcHandleProviderOrDriverChange');
 end;
end;

procedure TFormDatabaseBrowser.ValidateLocationFileType;
var Prov,Curr,Ext:LongString;
 function FindByExt(Ext:LongString):LongString;
 var i:Integer; Home,Base:LongString; Items:TStrings;
 begin
  Result:='';
  if (Ext='') then Exit;
  Base:=ExtractBaseName(Curr);
  Items:=ComboBoxDbcDatabase.Items;
  Home:=AdaptFileName(ExtractFileDir(Curr));
  // Find same dir/name file by ext
  for i:=0 to Items.Count-1 do
  if SameText(Base,ExtractBaseName(Items[i])) then
  if SameText(Home,AdaptFileName(ExtractFileDir(Items[i]))) then
  if SameText(Ext,ExtractFileExt(Items[i])) then Exit(Items[i]);
  // Find same name file by ext
  for i:=0 to Items.Count-1 do
  if SameText(Base,ExtractBaseName(Items[i])) then
  if SameText(Ext,ExtractFileExt(Items[i])) then Exit(Items[i]);
  // Find any file by ext
  for i:=0 to Items.Count-1 do
  if SameText(Ext,ExtractFileExt(Items[i])) then Exit(Items[i]);
 end;
begin
 try
  Curr:=Trim(ComboBoxDbcDatabase.Text);
  Prov:=Trim(ComboBoxDbcProvider.Text);
  Ext:=ExtractFileExt(Curr);
  if (WordIndex(Prov,'SQLite SQLite3',ScanSpaces)>0) then begin
   if (WordIndex(Ext,'.db .sqlite .sqlite3',ScanSpaces)=0) then begin
    ComboBoxDbcDatabase.Text:=TrimDef(FindByExt('.db'),Curr);
   end;
  end;
  if (WordIndex(Prov,'IB FB Firebird Interbase',ScanSpaces)>0) then begin
   if (Ext<>'') and (WordIndex(Ext,'.fdb',ScanSpaces)=0) then begin
    ComboBoxDbcDatabase.Text:=TrimDef(FindByExt('.fdb'),Curr);
   end;
  end;
  UpdateConnectionURI;
 except
  on E:Exception do BugReport(E,Self,'ValidateLocationFileType');
 end;
end;

procedure TFormDatabaseBrowser.DbcHandleAccountChange;
var cs:LongString;
begin
 try
  ComboBoxDbcAuth.ItemIndex:=Max(0,ComboBoxDbcAuth.ItemIndex);
  ComboBoxDbcSecr.ItemIndex:=Max(0,ComboBoxDbcSecr.ItemIndex);
  cs:=DbcAccountApply(ComboBoxDbcConnectionString.Text);
  ComboBoxDbcConnectionString.Text:=cs;
  UpdateConnectionURI;
 except
  on E:Exception do BugReport(E,Self,'DbcHandleAccountChange');
 end;
end;

function TFormDatabaseBrowser.DbcAccountApply(cs:LongString):LongString;
var au,ui,se,pw:LongString; mode:Integer;
begin
 Result:=cs;
 try
  mode:=ComboBoxDbcPwdCryptMode.ItemIndex;
  se:=ComboBoxDbcSecr.Items.Text; pw:=Trim(EditDbcPWD.Text);
  au:=ComboBoxDbcAuth.Items.Text; ui:=Trim(ComboBoxDbcUID.Text);
  if CheckBoxDbcSecr.Checked and (pw<>'') then cs:=db_subst_connectionString(cs,se,pw);
  if CheckBoxDbcAuth.Checked and (ui<>'') then cs:=db_subst_connectionString(cs,au,ui);
  cs:=TDbEngineAssistant.EncryptConnectionString(cs,mode);
  Result:=cs;
 except
  on E:Exception do BugReport(E,Self,'DbcHandleAccountChange');
 end;
end;

function TFormDatabaseBrowser.DbcUriApply(cs:LongString):LongString;
begin
 Result:=Trim(cs);
 if CheckBoxConnectionURI.Checked
 then Result:=ConnectionStringToURI(cs);
end;

procedure TFormDatabaseBrowser.DbcHandleLocationChange;
var cs,sa,sv,da,dv:LongString;
begin
 try
  AdaptDbcDatabaseName;
  ComboBoxDbcServAl.ItemIndex:=Max(0,ComboBoxDbcServAl.ItemIndex);
  ComboBoxDbcDataAl.ItemIndex:=Max(0,ComboBoxDbcDataAl.ItemIndex);
  cs:=ComboBoxDbcConnectionString.Text;
  sa:=ComboBoxDbcServAl.Items.Text; sv:=Trim(ComboBoxDbcServer.Text);
  da:=ComboBoxDbcDataAl.Items.Text; dv:=Trim(ComboBoxDbcDatabase.Text);
  if CheckBoxDbcMergeLocation.Checked and (sv<>'') and (dv<>'') then dv:=Trim(sv)+':'+Trim(dv);
  if CheckBoxDbcServer.Checked and (sv<>'') then cs:=db_subst_connectionString(cs,sa,sv);
  if CheckBoxDbcSource.Checked and (dv<>'') then cs:=db_subst_connectionString(cs,da,dv);
  ComboBoxDbcConnectionString.Text:=cs;
  UpdateConnectionURI;
 except
  on E:Exception do BugReport(E,Self,'DbcHandleLocationChange');
 end;
end;

function TFormDatabaseBrowser.DbcSqlQuerySelectAllTablesRead:LongString;
var Samples:TText; i,p:Integer; Line,sn,sv,dr:LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 if (dbc=0) then Exit;
 try
  dr:=CookieScan(db_ctrl(dbc,'ConnectionStringInit'),'Driver',Ord(';'));
  dr:=dr+EOL+db_ctrl(dbc,'Provider');
  dr:=UpcaseStr(Trim(dr));
  if IsEmptyStr(dr) then Exit;
  Samples:=ExtractListSection(SysIniFile,'[DbApi.SQL.Query.SelectAllTables]',efAsIs);
  try
   for i:=Samples.Count-1 downto 0 do begin
    Line:=SysUtils.Trim(Samples[i]);
    p:=ExtractNameValuePair(Line,sn,sv); if (p=0) then continue;
    if not IsLexeme(sn,lex_Name) then continue;
    if IsEmptyStr(sv) then continue;
    p:=Pos(UpcaseStr(sn),UpcaseStr(dr)); if (p=0) then continue;
    Result:=sv;
    break;
   end;
  finally
   Kill(Samples);
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcSqlQuerySelectAllTablesRead');
 end;
end;


function TFormDatabaseBrowser.DbcTimePrompt(When:TDateTime; cPad:Char=' '):LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 if (When>0)
 then Result:=StdDateTimeStr(OleTimeToMs(When))
 else Result:=StringOfChar(cPad,Length(XmasDateTimeSample));
 Result:=Result+' => ';
end;

function TFormDatabaseBrowser.DbcPad(cPad:Char=' '):LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 Result:=DbcTimePrompt(0,cPad);
end;

function TFormDatabaseBrowser.FormatDate(tm:Double; typ:Integer):LongString;
var ms:Double;
begin
 Result:='';
 if (Self=nil) then Exit;
 ms:=DbCon.DbDateTimeToMs(tm,typ,DbcEngineId);
 if CheckBoxDbcShowDateAsReal.Checked then begin
  if CheckBoxDbcUseMsSinceXmas.Checked
  then Result:=Format('%g',[ms])
  else Result:=Format('%g',[tm]);
 end else begin
  Result:=StrTimeFmt(ComboBoxDbcFormatDate.Text,ms);
 end;
end;

function TFormDatabaseBrowser.FormatTime(tm:Double; typ:Integer):LongString;
var ms:Double;
begin
 Result:='';
 if (Self=nil) then Exit;
 ms:=DbCon.DbDateTimeToMs(tm,typ,DbcEngineId);
 if CheckBoxDbcShowTimeAsReal.Checked then begin
  if CheckBoxDbcUseMsSinceXmas.Checked
  then Result:=Format('%g',[ms])
  else Result:=Format('%g',[tm]);
 end else begin
  Result:=StrTimeFmt(ComboBoxDbcFormatTime.Text,ms);
 end;
end;

function TFormDatabaseBrowser.FormatTimeStamp(tm:Double; typ:Integer):LongString;
var ms,dt:Double; HasDate,HasTime:Boolean;
begin
 Result:='';
 if (Self=nil) then Exit;
 ms:=DbCon.DbDateTimeToMs(tm,typ,DbcEngineId);
 if CheckBoxDbcShowTimeStampAsReal.Checked then begin
  if CheckBoxDbcUseMsSinceXmas.Checked
  then Result:=Format('%g',[ms])
  else Result:=Format('%g',[tm]);
 end else begin
  if not CheckBoxDbcCompactTimeStamp.Checked
  then begin HasDate:=True; HasTime:=True; end
  else begin dt:=MsToOleTime(ms); HasDate:=(dt>=1); HasTime:=(Frac(dt)>=MachEps*4); end;
  if not HasDate then Result:=StrTimeFmt(ComboBoxDbcFormatTime.Text,ms) else
  if HasDate and not HasTime then Result:=StrTimeFmt(ComboBoxDbcFormatDate.Text,ms) else
  Result:=StrTimeFmt(ComboBoxDbcFormatTimeStamp.Text,ms);
 end;
end;

function TFormDatabaseBrowser.DbcEngineId:Integer;
begin
 Result:=0;
 if (Self=nil) then Exit;
 Result:=ComboBoxDbcEngine.ItemIndex;
end;

function TFormDatabaseBrowser.DbcSqlQuery:LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 Result:=SysUtils.Trim(MemoDbcSqlQuery.Text);
end;

function TFormDatabaseBrowser.DbcEngineName:LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 Result:=db_engine_name(ComboBoxDbcEngine.ItemIndex);
end;

function TFormDatabaseBrowser.DbcConnectionString:LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 Result:=SysUtils.Trim(ComboBoxDbcConnectionString.Text);
end;

procedure TFormDatabaseBrowser.DbcLog(When:TDateTime; What:LongString);
begin
 if (Self=nil) then Exit;
 if (When>0) then What:=DbcTimePrompt(When)+What;
 MemoDbcLog.Lines.Add(What);
 MemoDbcLog.SelStart:=MaxInt;
end;

procedure TFormDatabaseBrowser.DbcLogFail(When:TDateTime; What:LongString);
var bc,ec:Integer;
begin
 if (Self=nil) then Exit;
 DbcLog(When,What);
 if (dbc<>0) then begin
  bc:=db_bugsCount(dbc); ec:=db_errorsCount(dbc);
  DbcLog(When,Format('%d bug(s) and %d error(s) found.',[bc,ec]));
  DbcInvalidateErrorsBugs;
 end;
end;

procedure TFormDatabaseBrowser.DbcErrorsLog(When:TDateTime; What:LongString);
begin
 if (Self=nil) then Exit;
 if (When>0) then What:=DbcTimePrompt(When)+What;
 MemoDbcErrorsLog.Lines.Add(What);
 MemoDbcErrorsLog.SelStart:=MaxInt;
end;

procedure TFormDatabaseBrowser.DbcFree;
begin
 if (Self=nil) then Exit;
 if (dbc<>0) then begin
  DbcLog(Now,'Free '+db_ctrl(dbc,'EngineName')+' ver '+db_ctrl(dbc,'Version')+' object.');
  db_free(dbc);
  dbc:=0;
 end;
end;

function UptimeStr(const Prefix:LongString; t0,t1:Double):LongString;
var dt:Double; ts,dd,hh,mm,ss:Integer; ut:LongString;
begin
 ut:='';
 if (t0>0) and (t1>=t0) then begin
  dt:=(t1-t0)/1000; ts:=Round(dt);
  dd:=ts div (60*60*24); ts:=ts mod (60*60*24);
  hh:=ts div (60*60);    ts:=ts mod (60*60);
  mm:=ts div (60);       ts:=ts mod (60);
  ss:=ts; ts:=Round(dt);
  ut:=Prefix+Format('%d-%2.2d:%2.2d:%2.2d = %d sec',[dd,hh,mm,ss,ts]);
 end;
 Result:=ut;
end;

procedure TFormDatabaseBrowser.DbcUpdateState;
var s,drv:LongString; col:TColor; state:Integer; ot:Double;
begin
 if (Self=nil) then Exit;
 try
  col:=clBtnFace;
  if db_ref(dbc).Ok then begin
   state:=BoolToInt(db_active(dbc)); if (state>0) then col:=clLime else col:=clYellow;
   drv:=CookieScan(db_ctrl(dbc,'ConnectionStringInit'),'Driver',Ord(';'));
   s:='Connection '+db_ctrl(dbc,'EngineName')+' ver '+db_ctrl(dbc,'Version')+'; ';
   if (s<>'') then s:=s+db_ctrl(dbc,'Provider')+'; '; if (s<>'') and (drv<>'') then s:=s+drv+'; ';
   if (s<>'') then s:=TrimChars(s,ScanSpaces,ScanSpaces)+' => '+ExtractWord(1+BoolToInt(state>0),'Closed,Opened',ScanSpaces);
   if (s<>'') then if Str2Real(db_ctrl(dbc,'TimeStampOpen'),ot) and (ot>0) then s:=s+UptimeStr('; Uptime ',ot,msecnow);
  end else s:='Connection is not initialized.';
  StatusBarDbcState.SimpleText:=s;
  StatusBarDbcState.Color:=col;
 except
  on E:Exception do BugReport(E,Self,'UpdateDbcState');
 end;
end;

procedure TFormDatabaseBrowser.DbcHandleErrors;
var Lines:TStrings; i,ec,bc:Integer; line:LongString;
begin
 if (Self=nil) then Exit;
 if (dbc<>0) then
 try
  bc:=db_bugsCount(dbc);    if (bc>0) then Inc(dbcBugs,bc);
  ec:=db_errorsCount(dbc);  if (ec>0) then Inc(dbcErrors,ec);
  if (ec>0) then begin
   MemoDbcErrorsLog.Lines.BeginUpdate;
   Lines:=TStringList.Create;
   try
    Lines.Text:=db_errors(dbc);
    for i:=0 to Lines.Count-1 do begin
     line:=Lines.Strings[i];
     DbcErrorsLog(Now,line);
    end;
   finally
    Lines.Free;
    db_bugsClear(dbc);
    db_errorsClear(dbc);
    MemoDbcErrorsLog.Lines.EndUpdate;
   end;
   MemoDbcErrorsLog.SelStart:=MaxInt;
  end;
  db_bugsClear(dbc);
  db_errorsClear(dbc);
 except
  on E:Exception do BugReport(E,Self,'HandleErrors');
 end;
end;

procedure TFormDatabaseBrowser.DbcInvalidateErrorsBugs;
begin
 lastBugs:=dbcBugs-1;
 lastErrors:=dbcErrors-1;
end;

function TFormDatabaseBrowser.EmptyDataMarker:LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 Result:=ComboBoxDbcEmptyDataMarker.Text;
end;

function TFormDatabaseBrowser.DbcMaxRecords:LongString;
begin
 Result:='';
 if (Self=nil) then Exit;
 Result:=SysUtils.Trim(ComboBoxDbcMaxRecords.Text);
end;

procedure TFormDatabaseBrowser.DbcSqlResultClear;
begin
 if (Self=nil) then Exit;
 try
  StringGridDbcSqlResult.ColCount:=2; // Col 0 is table row number
  StringGridDbcSqlResult.RowCount:=3; // Row 0 is TYPE, Row 1 is NAME of Field
  DbcSqlResultCells(0,0,'TYPE=>');  DbcSqlResultCells(1,0,EmptyDataMarker);
  DbcSqlResultCells(0,1,'NAME=>');  DbcSqlResultCells(1,1,EmptyDataMarker);
  DbcSqlResultCells(0,2,'1');       DbcSqlResultCells(1,2,EmptyDataMarker);
  StringGridDbcSqlResult.ColWidths[0]:=StringGridDbcSqlResult.DefaultColWidth;
  StringGridDbcSqlResult.ColWidths[1]:=StringGridDbcSqlResult.DefaultColWidth;
 except
  on E:Exception do BugReport(E,Self,'DbcClearSqlResult');
 end;
end;

procedure TFormDatabaseBrowser.DbcApplyMaxRecords;
begin
 if (Self=nil) then Exit;
 MaxRecords:=Max(0,StrToIntDef(DbcMaxRecords,0));
 if (MaxRecords>0) then DbcLog(Now,'Applied MaxRecords = '+IntToStr(MaxRecords));
end;

procedure TFormDatabaseBrowser.RunActionExternal(Action:TAction);
var cmd:LongString;
begin
 if (Self=nil) then Exit;
 if (Action=nil) then Exit;
 try
  cmd:='';
  if ReadIniFileString(SysIniFile,SectDbApiDatabaseBrowserActions(1),Action.Name+'%s',cmd,efConfigNC,svConfig) then begin
   if MaybeEnvStr(cmd) then cmd:=ExpEnv(cmd); cmd:=SysUtils.Trim(cmd);
   if IsLexeme(cmd,lex_AtCmnd) then SendToMainConsole(cmd+EOL);
  end;
 except
  on E:Exception do BugReport(E,Self,'RunActionExternal');
 end;
end;

procedure TFormDatabaseBrowser.DbcSqlResultCells(Col,Row:Integer; Data:LongString; Expand:Boolean=false);
begin
 if (Self=nil) then Exit;
 if (Col<0) or (Row<0) then Exit;
 try
  if (Col>StringGridDbcSqlResult.ColCount-1) then begin
   if Expand then StringGridDbcSqlResult.ColCount:=Col+1 else Exit;
  end;
  if (Row>StringGridDbcSqlResult.RowCount-1) then begin
   if Expand then StringGridDbcSqlResult.RowCount:=Row+1 else Exit;
  end;
  if (Data='') then Data:=EmptyDataMarker;
  StringGridDbcSqlResult.Cells[Col,Row]:=Data;
 except
  on E:Exception do BugReport(E,Self,'DbcSqlResultCells');
 end;
end;

function TFormDatabaseBrowser.db_query_dbapimasterkey;
begin
 Result:=TDbEngineAssistant.DbApiMasterKey;
end;

procedure TFormDatabaseBrowser.DbcUpdatePwdCryptResult;
var pwd,org:LongString; mode:Integer; col:TColor;
begin
 if (Self=nil) then Exit;
 try
  pwd:=Trim(EditDbcPwd.Text); org:=pwd;
  mode:=ComboBoxDbcPwdCryptMode.ItemIndex;
  pwd:=TDbEngineAssistant.EncryptPassword(pwd,mode);
  if not (mode in pem_Secure) then col:=clBlack else
  if (org=TDbEngineAssistant.DecryptPassword(pwd,mode))
  then col:=clGreen else col:=clRed;
  LabelDbcPwdCryptMode.Font.Color:=col;
  EditDbcPwdCryptResult.Text:=pwd;
 except
  on E:Exception do BugReport(E,Self,'DbcUpdatePwdCryptResult');
 end;
end;

procedure TFormDatabaseBrowser.TimerDbcMainTimer(Sender: TObject);
begin
 if (Self=nil) then Exit;
 try
  DbcUpdateState;
  if (dbc=0) then begin
   if (ComboBoxDbcReadTables.Items.Count>0)
   then DbcReadTablesClear;
  end;
  ActionDbcConnect.Enabled:=(dbc=0);
  ActionDbcDisconnect.Enabled:=(dbc<>0);
  ActionDbcReadTables.Enabled:=(dbc<>0);
  ActionDbcExecute.Enabled:=(dbc<>0) and (MemoDbcSqlQuery.Lines.Count>0);
  if (dbcBugs<>lastBugs) or (dbcErrors<>LastErrors) then begin
   LabelDbcBugsCount.Caption:=Format('Bugs:   %d',[dbcBugs]);
   LabelDbcErrorsCount.Caption:=Format('Errors: %d',[dbcErrors]);
   if (dbcBugs<>0) then LabelDbcBugsCount.Color:=clYellow else LabelDbcBugsCount.ParentColor:=true;
   if (dbcErrors<>0) then LabelDbcErrorsCount.Color:=clYellow else LabelDbcErrorsCount.ParentColor:=true;
   if (dbcBugs<>0) or (dbcErrors<>0) then ActionDbcStatusReset.ImageIndex:=252 else ActionDbcStatusReset.ImageIndex:=253;
   lastBugs:=dbcBugs; LastErrors:=dbcErrors;
  end;
  if (PageControlDbConnection.ActivePage=TabSheetDbcAccount) then begin
   if (EditDbcPWD.PasswordChar=#0) then begin
    ActionDbcPasswordHideShow.Caption:='Hide';
    BitBtnDbcPasswordHideShow.ImageIndex:=177;
   end else begin
    ActionDbcPasswordHideShow.Caption:='Show';
    BitBtnDbcPasswordHideShow.ImageIndex:=176;
   end;
   DbcUpdatePwdCryptResult;
   ActionDbcPwdCryptCopy.Enabled:=(EditDbcPwdCryptResult.Text<>'');
  end;
  if (PageControlSqlQuery.ActivePage=TabSheetDbcSqlQuery) then begin
   ActionDbcSqlQuerySelectAll.Enabled:=(MemoDbcSqlQuery.Lines.Count>0);
   ActionDbcSqlQueryUnselect.Enabled:=(MemoDbcSqlQuery.SelLength>0);
   ActionDbcSqlQueryClear.Enabled:=(MemoDbcSqlQuery.Lines.Count>0);
   ActionDbcSqlQueryCut.Enabled:=ActionDbcSqlQueryUnselect.Enabled;
   ActionDbcSqlQueryCopy.Enabled:=ActionDbcSqlQueryUnselect.Enabled;
   ActionDbcSqlQueryPaste.Enabled:=Clipboard.HasFormat(CF_TEXT);
  end;
  if (PageControlSqlQuery.ActivePage=TabSheetDbcSqlQuerySamples) then begin
   ActionDbcSqlQuerySamplesSelectAll.Enabled:=(MemoDbcSqlQuerySamples.Lines.Count>0);
   ActionDbcSqlQuerySamplesUnselect.Enabled:=(MemoDbcSqlQuerySamples.SelLength>0);
   ActionDbcSqlQuerySamplesCopy.Enabled:=ActionDbcSqlQuerySamplesUnselect.Enabled;
  end;
  if (PageControlDbcOutput.ActivePage=TabSheetDbcLog) then begin
   ActionDbcLogSelectAll.Enabled:=(MemoDbcLog.Lines.Count>0);
   ActionDbcLogUnselect.Enabled:=(MemoDbcLog.SelLength>0);
   ActionDbcLogClear.Enabled:=(MemoDbcLog.Lines.Count>0);
   ActionDbcLogCopy.Enabled:=ActionDbcLogUnselect.Enabled;
  end;
  if (PageControlDbcOutput.ActivePage=TabSheetDbcErrorsLog) then begin
   ActionDbcErrorsLogSelectAll.Enabled:=(MemoDbcErrorsLog.Lines.Count>0);
   ActionDbcErrorsLogUnselect.Enabled:=(MemoDbcErrorsLog.SelLength>0);
   ActionDbcErrorsLogClear.Enabled:=(MemoDbcErrorsLog.Lines.Count>0);
   ActionDbcErrorsLogCopy.Enabled:=ActionDbcErrorsLogUnselect.Enabled;
  end;
  ActionDbcMenuTables.Enabled:=(dbc<>0) and (ComboBoxDbcReadTables.Items.Count>0);
 except
  on E:Exception do BugReport(E,Self,'TimerDbcMainTimer');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcConnectExecute(Sender: TObject);
var cs:LongString;
begin
 try
  DbcFree;
  cs:=DbcAccountApply(DbcConnectionString);
  cs:=DbcUriApply(cs); // Use URI format?
  DbcLog(Now,'Connect: '+cs);
  dbc:=db_connection(DbcEngineId,cs);
  if (db_type(dbc)=db_type_connection)
  then DbcLog(Now,'Init '+db_ctrl(dbc,'EngineName')+' ver '+db_ctrl(dbc,'Version')+' object.');
  if db_open(dbc,adConnectUnspecified) then begin
   DbcLog(Now,'Database connected.');
  end else begin
   DbcLogFail(Now,'Could not connect to database.');
   //ActionDbcDisconnect.Execute;
   DbcInvalidateErrorsBugs;
   DbcSqlResultClear;
  end;
  if (dbc<>0) and not db_ref(dbc).Ok then DbcFree;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcConnectExecute');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcDisconnectExecute(Sender: TObject);
begin
 DbcFree; // Free object
 DbcInvalidateErrorsBugs;
 DbcReadTablesClear;
end;

procedure TFormDatabaseBrowser.ActionDbcProviderDriverUpdateExecute(Sender: TObject);
begin
 UpdateProviders(true);
 UpdateOdbcDrivers(true);
 DbcHandleProviderChange;
 ValidateLocationFileType;
 DbcHandleLocationChange;
 DbcHandleAccountChange;
end;

procedure TFormDatabaseBrowser.ActionDbcPasswordHideShowExecute(Sender: TObject);
begin
 if (EditDbcPWD.PasswordChar=#0)
 then EditDbcPWD.PasswordChar:='*'
 else EditDbcPWD.PasswordChar:=#0;
end;

procedure TFormDatabaseBrowser.ActionDbcExecuteExecute(Sender: TObject);
var rst:Integer; id,sv:LongString; ic,tp,tl,numcol,numrow:Integer; ft:TFieldType;
var bvm:Integer; bt:LongString;
begin
 if (dbc<>0) then
 try
  DbcSqlResultClear;
  DbcApplyMaxRecords;
  tl:=db_beginTrans(dbc);
  if (tl>0) then begin
   DbcLog(Now,Format('Begin Transaction level %d.',[tl]));
   DbcHandleErrors;
  end else begin
   DbcLogFail(Now,'Fail on Begin Transaction.');
   DbcHandleErrors;
   Exit;
  end;
  DbcLog(Now,Format('Execute: %s',[DbcSqlQuery]));
  rst:=db_execute(dbc,DbcSqlQuery,adCmdText).ref;
  try
   if (rst<>0) then begin
    numrow:=0;
    numcol:=db_fieldscount(rst);
    DbcLog(Now,Format('Executed. %d field(s) table received.',[numcol]));
    if (numcol>0) then begin
     StringGridDbcSqlResult.ColCount:=1+numcol;
     while not db_eof(rst) do begin
      inc(numrow);
      for ic:=0 to numcol-1 do begin
       id:=db_fieldsNames(rst,ic); // Field Name
       tp:=db_fieldsTypes(rst,id); // Field Type
       sv:=db_fieldsAsString(rst,id,'r',''); // Field Value
       if (DbcEngineId=db_engine_ado) then begin
        case tp of
         adDBDate      : if CheckBoxDbcFormatDate.Checked then sv:=FormatDate(db_fieldsAsFloat(rst,id,'r',0),tp);
         adDBTime      : if CheckBoxDbcFormatTime.Checked then sv:=FormatDate(db_fieldsAsFloat(rst,id,'r',0),tp);
         adDate,
         adFileTime,
         adDBFileTime,
         adDBTimeStamp : if CheckBoxDbcFormatTimeStamp.Checked then sv:=FormatTimeStamp(db_fieldsAsFloat(rst,id,'r',0),tp);
        end;
       end;
       if (DbcEngineId in [db_engine_sqldb,db_engine_zeos]) then begin
        ft:=TFieldType(tp);
        case ft of
         ftDate      : if CheckBoxDbcFormatDate.Checked then sv:=FormatDate(db_fieldsAsFloat(rst,id,'r',0),tp);
         ftTime      : if CheckBoxDbcFormatTime.Checked then sv:=FormatDate(db_fieldsAsFloat(rst,id,'r',0),tp);
         ftDateTime,
         ftTimeStamp : if CheckBoxDbcFormatTimeStamp.Checked then sv:=FormatTimeStamp(db_fieldsAsFloat(rst,id,'r',0),tp);
        end;
       end;
       if (numrow=1) then begin // Header row[0/1]=[type/name]
        DbcSqlResultCells(ic+1,0,DbCon.FieldTypeCodeToString(tp,DbcEngineId));
        DbcSqlResultCells(ic+1,1,id);
       end;
       // Write row number to column 0
       DbcSqlResultCells(0,numrow+1,IntToStr(numrow),true);
       // Prepare BLOB view
       if DbCon.IsBlobFieldTypeCode(tp,DbcEngineId) then begin
        // 0:marker,1:detect,2:as_is,3:b64,4:hex,5:pct
        bvm:=ComboBoxDbcBlobViewMode.ItemIndex;
        bt:='';
        case bvm of
         0: bt:='bin';
         1: bt:=TrimDef(DbCon.DetectBlobImageType(sv),'bin');
         2: ;
         3: sv:=base64_encode(sv);
         4: sv:=hex_encode(sv);
         5: sv:=percent_encode(sv,Pct_DropChars_Default+[#127..#255]);
        end;
        if (bt<>'') then sv:='BLOB/'+bt;
       end;
       // Write data to cell of SQL Result table
       DbcSqlResultCells(ic+1,numrow+1,sv,true);
      end;
      if (numrow=MaxRecords) then break;
      if not db_movenext(rst) then break;
     end;
     DbcLog(Now,Format('%d row(s) read.',[numrow]));
    end;
    if db_commitTrans(dbc) then begin
     DbcLog(Now,'Commit Transaction.');
     tl:=0;
    end else begin
     DbcLogFail(Now,'Fail on Commit Transaction.');
    end;
   end else begin
    DbcLogFail(Now,'Could not execute SQL Query.');
    DbcHandleErrors;
   end;
  finally
   if (tl>0) then begin
    if db_rollbackTrans(dbc)
    then DbcLog(Now,'Rollback Transaction.')
    else DbcLogFail(Now,'Fail on Rollback Transaction.');
   end;
   DbcHandleErrors;
   db_free(rst);
  end;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcExecute');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcStatusResetExecute(Sender: TObject);
begin
 dbcBugs:=0;
 dbcErrors:=0;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQuerySelectAllExecute(Sender: TObject);
begin
 MemoDbcSqlQuery.SelectAll;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQueryUnselectExecute(Sender: TObject);
begin
 MemoDbcSqlQuery.SelLength:=0;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQueryClearExecute(Sender: TObject);
begin
 MemoClear(MemoDbcSqlQuery);
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQueryCutExecute(Sender: TObject);
begin
 MemoCutToClipboard(MemoDbcSqlQuery);
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQueryCopyExecute(Sender: TObject);
begin
 MemoCopyToClipboard(MemoDbcSqlQuery);
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQueryPasteExecute(Sender: TObject);
begin
 MemoPasteFromClipboard(MemoDbcSqlQuery);
end;

procedure TFormDatabaseBrowser.SaveDialogDbcSqlQueryTableSaveTypeChange(Sender: TObject);
begin
 try
  case SaveDialogDbcSqlQueryTableSave.FilterIndex of
   1: SaveDialogDbcSqlQueryTableSave.DefaultExt:='.csv';
   2: SaveDialogDbcSqlQueryTableSave.DefaultExt:='.txt';
   3: SaveDialogDbcSqlQueryTableSave.DefaultExt:='.xml';
  end;
  with SaveDialogDbcSqlQueryTableSave do FileName:=ChangeFileExt(FileName,DefaultExt);
 except
  on E:Exception do BugReport(E,Self,'SaveDialogDbcSqlQueryTableSaveTypeChange');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQueryTableSaveExecute(Sender:TObject);
var dir,fln,ext,tab,msg:LongString; rst,tl,numcol:Integer;
begin
 try
  if db_active(dbc) then begin
   fln:=SaveDialogDbcSqlQueryTableSave.FileName; tab:='';
   ext:=TrimDef(ExtractFileExt(fln),SaveDialogDbcSqlQueryTableSave.DefaultExt);
   dir:=TrimDef(ExtractFileDir(fln),SessionManager.VarTmpDir);
   tab:=SqlDbAssistant.GetStatementInfo(DbcSqlQuery).TableName;
   if (tab<>'') then tab:=ExtractPhrase(1,tab,ScanSpaces);
   if (tab='') then tab:=ComboBoxDbcReadTables.Text;
   if (tab='') then tab:=ExtractBaseName(fln);
   tab:=Lowercase(TrimDef(tab,'table'));
   SaveDialogDbcSqlQueryTableSave.FileName:=AddPathDelim(dir)+tab+ext;
   if SaveDialogDbcSqlQueryTableSave.Execute then begin
    fln:=SaveDialogDbcSqlQueryTableSave.FileName;
    fln:=ChangeFileExt(fln,SaveDialogDbcSqlQueryTableSave.DefaultExt);
    SaveDialogDbcSqlQueryTableSave.InitialDir:=ExtractFileDir(fln);
    if FileExists(fln) then begin
     msg:=RusEng('Файл '+ExtractFileNameExt(fln)+' уже существует.'+EOL+'Перезаписать его?',
                 'File '+ExtractFileNameExt(fln)+' is already exist.'+EOL+'Overwrite it?');
     if (YesNo(msg,ControlPosParams(MemoDbcSqlQuery))<>mrYes) then Exit;
    end;
    tl:=db_beginTrans(dbc);
    if (tl>0) then begin
     DbcLog(Now,Format('Begin Transaction level %d.',[tl]));
     DbcHandleErrors;
    end else begin
     DbcLogFail(Now,'Fail on Begin Transaction.');
     DbcHandleErrors;
     Exit;
    end;
    DbcLog(Now,Format('Execute: %s',[DbcSqlQuery]));
    rst:=db_execute(dbc,DbcSqlQuery,adCmdText).ref;
    try
     if (rst<>0) then begin
      numcol:=db_fieldscount(rst);
      DbcLog(Now,Format('Executed. %d field(s) table received.',[numcol]));
      if (numcol>0) then begin
       if db_save(rst,fln,Ord(dfAny)) then begin
        DbcLog(Now,'Table saved to '+fln);
        if FileExists(fln)
        then DbcLog(Now,'File byte size '+IntToStr(GetFileSize(fln)))
        else DbcLog(Now,'File not found '+fln);
       end else begin
        DbcLog(Now,'Could not save '+fln);
       end;
      end else DbcLog(Now,'No data found to save.');
      if db_commitTrans(dbc) then begin
       DbcLog(Now,'Commit Transaction.');
       tl:=0;
      end else begin
       DbcLogFail(Now,'Fail on Commit Transaction.');
      end;
     end else begin
      DbcLogFail(Now,'Could not execute SQL Query.');
      DbcHandleErrors;
     end;
    finally
     if (tl>0) then begin
      if db_rollbackTrans(dbc)
      then DbcLog(Now,'Rollback Transaction.')
      else DbcLogFail(Now,'Fail on Rollback Transaction.');
     end;
     DbcHandleErrors;
     db_free(rst);
    end;
   end;
  end else DbcLog(Now,'Could not save inactive connection.');
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlQueryTableSaveExecute');
 end;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcProviderChange(Sender: TObject);
begin
 DbcHandleProviderChange;
 ValidateLocationFileType;
 DbcHandleLocationChange;
 DbcHandleAccountChange;
end;

procedure TFormDatabaseBrowser.ComboBoxOdbcDriverChange(Sender: TObject);
begin
 DbcHandleProviderChange;
 ValidateLocationFileType;
 DbcHandleLocationChange;
 DbcHandleAccountChange;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcUIDChange(Sender: TObject);
begin
 DbcHandleAccountChange;
end;

procedure TFormDatabaseBrowser.EditDbcPWDChange(Sender: TObject);
begin
 DbcHandleAccountChange;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcPwdCryptModeChange(Sender: TObject);
begin
 DbcHandleAccountChange;
end;

procedure TFormDatabaseBrowser.ActionDbcFindDatabaseExecute(Sender: TObject);
begin
 try
  if (OpenDialogDbcDatabase.InitialDir='')
  then OpenDialogDbcDatabase.InitialDir:=ExtractFileDir(ComboBoxDbcDatabase.Text);
  if (OpenDialogDbcDatabase.FileName='')
  then OpenDialogDbcDatabase.FileName:=SysUtils.Trim(ComboBoxDbcDatabase.Text);
  if OpenDialogDbcDatabase.Execute then begin
   ComboBoxDbcDatabase.Text:=SysUtils.Trim(OpenDialogDbcDatabase.FileName);
   DbcHandleLocationChange;
  end;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcFindDatabaseExecute');
 end;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcServerChange(Sender: TObject);
begin
 DbcHandleLocationChange;
end;

procedure TFormDatabaseBrowser.CheckBoxDbcMergeLocationClick(Sender: TObject);
begin
 DbcHandleLocationChange;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcDatabaseChange(Sender: TObject);
begin
 DbcHandleLocationChange;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQuerySamplesSelectAllExecute(Sender: TObject);
begin
 MemoDbcSqlQuerySamples.SelectAll;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQuerySamplesUnselectExecute(Sender: TObject);
begin
 MemoDbcSqlQuerySamples.SelLength:=0;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlQuerySamplesCopyExecute(Sender: TObject);
begin
 MemoCopyToClipboard(MemoDbcSqlQuerySamples);
end;

procedure TFormDatabaseBrowser.ActionDbcLogSelectAllExecute(Sender: TObject);
begin
 MemoDbcLog.SelectAll;
end;

procedure TFormDatabaseBrowser.ActionDbcLogUnselectExecute(Sender: TObject);
begin
 MemoDbcLog.SelLength:=0;
end;

procedure TFormDatabaseBrowser.ActionDbcLogClearExecute(Sender: TObject);
begin
 MemoClear(MemoDbcLog);
end;

procedure TFormDatabaseBrowser.ActionDbcLogCopyExecute(Sender: TObject);
begin
 MemoCopyToClipboard(MemoDbcLog);
end;

procedure TFormDatabaseBrowser.ActionDbcErrorsLogSelectAllExecute(Sender: TObject);
begin
 MemoDbcErrorsLog.SelectAll;
end;

procedure TFormDatabaseBrowser.ActionDbcErrorsLogUnselectExecute(Sender: TObject);
begin
 MemoDbcErrorsLog.SelLength:=0;
end;

procedure TFormDatabaseBrowser.ActionDbcErrorsLogClearExecute(Sender: TObject);
begin
 MemoClear(MemoDbcErrorsLog);
end;

procedure TFormDatabaseBrowser.ActionDbcErrorsLogCopyExecute(Sender: TObject);
begin
 MemoCopyToClipboard(MemoDbcErrorsLog);
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultClearExecute(Sender: TObject);
begin
 DbcSqlResultClear;
end;

procedure TFormDatabaseBrowser.ActionDatabaseBrowserManualExecute(Sender: TObject);
begin
 RunActionExternal(ActionDatabaseBrowserManual);
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultSelectAllExecute(Sender: TObject);
var R:TGridRect;
begin
 try
  R.Top:=2; R.Bottom:=StringGridDbcSqlResult.RowCount-1;
  R.Left:=1; R.Right:=StringGridDbcSqlResult.ColCount-1;
  StringGridDbcSqlResult.Selection:=R;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlResultSelectAllExecute');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultUnselectExecute(Sender: TObject);
var R:TGridRect;
begin
 try
  R:=StringGridDbcSqlResult.Selection;
  R.Bottom:=R.Top; R.Right:=R.Left;
  StringGridDbcSqlResult.Selection:=R;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlResultUnselectExecute');
 end;
end;

function TFormDatabaseBrowser.DbcSqlResultSelText(const R:TGridRect; FieldSeparator:Char=#9):LongString;
var row,col:Integer; line,cell,types,names:LongString; Lines:TStringList; head:Boolean;
begin
 Result:='';
 if (Self=nil) then Exit;
 try
  Lines:=TStringList.Create;
  try
   head:=false;
   types:=''; names:='';
   for row:=R.Top to R.Bottom do
   if (row>=0) and (row<=StringGridDbcSqlResult.RowCount-1) then begin
    line:='';
    for col:=R.Left to R.Right do
    if (col>=0) and (col<=StringGridDbcSqlResult.ColCount-1) then  begin
     if (line<>'') then line:=line+FieldSeparator;
     cell:=StringGridDbcSqlResult.Cells[col,row];
     line:=Line+cell; if head then continue;
     if (types<>'') then types:=types+FieldSeparator;
     cell:=StringGridDbcSqlResult.Cells[col,0]; types:=types+cell;
     if (names<>'') then names:=names+FieldSeparator;
     cell:=StringGridDbcSqlResult.Cells[col,1]; names:=names+cell;
    end;
    if not head then begin
     if CheckBoxDbcSqlResultHeadTypes.Checked then Lines.Add(types);
     if CheckBoxDbcSqlResultHeadNames.Checked then Lines.Add(names);
     head:=true;
    end;
    Lines.Add(line);
   end;
   Result:=Lines.Text;
  finally
   Lines.Free;
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcSqlResultSelText');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultCopyExecute(Sender: TObject);
begin
 try
  Clipboard.AsText:=DbcSqlResultSelText(StringGridDbcSqlResult.Selection);
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlResultCopyExecute');
 end;
end;

procedure SortStrings(Items:TStrings);
var Lines:TStringList;
begin
 Lines:=TStringList.Create;
 try
  Lines.Sorted:=true;
  Lines.Duplicates:=dupIgnore;
  Lines.Text:=Items.Text;
  Items.Text:=Lines.Text;
 finally
  Kill(Lines);
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcReadTablesExecute(Sender: TObject);
var cmd,id,sv,tabs,prop:LongString; rst,numrow,numcol,ic,tl,tp:Integer;
const TableNamesDelims=EolnDelims+[',',';'];
 procedure ComboBoxDbcReadTablesSortAndSelect;
 begin
  if CheckBoxDbcSortListOfTables.Checked
  then SortStrings(ComboBoxDbcReadTables.Items);
  if (ComboBoxDbcReadTables.Items.Count>0) // Select item if one not selected
  then ComboBoxDbcReadTables.ItemIndex:=Max(0,ComboBoxDbcReadTables.ItemIndex);
  DbcLog(Now,Format('Tables read: %d',[ComboBoxDbcReadTables.Items.Count]));
 end;
 procedure ComboBoxDbcReadTablesCheckAddTable(sv:LongString);
 begin
  if IsLexeme(sv,lex_Print) // Name is valid?
  then StringsAdd(ComboBoxDbcReadTables.Items,sv);
 end;
begin
 if (Self=nil) then Exit; if (dbc=0) then Exit;
 try
  DbcReadTablesClear;
  // case SQLDB,ZEOS: use Properties Connection.TableNames first
  if db_engineid(dbc) in [db_engine_sqldb,db_engine_zeos] then begin
   DbcLog(Now,'Try read Properties=Connection.TableNames');
   prop:=db_ctrl(dbc,'Properties=Connection.TableNames');
   if IsNonEmptyStr(prop) then begin
    DbcLog(Now,'Read: '+Trim(prop));
    tabs:=Trim(CookieScan(prop,'Connection.TableNames',Ord(';')));
    if IsNonEmptyStr(tabs) then begin
     DbcLog(Now,Format('TableNames: %s',[tabs]));
     for ic:=1 to PhraseCount(tabs,TableNamesDelims) do begin
      sv:=Trim(ExtractPhrase(ic,tabs,TableNamesDelims));
      ComboBoxDbcReadTablesCheckAddTable(sv);
     end;
     DbcHandleErrors;
     ComboBoxDbcReadTablesSortAndSelect;
     Exit;
    end;
   end;
   DbcHandleErrors;
  end;
  // Fallback - read tables by SQL request.
  cmd:=DbcSqlQuerySelectAllTablesRead;
  if not IsEmptyStr(cmd) then begin
   DbcLog(Now,'Try ReadTables by SQL Query: '+cmd);
   tl:=db_beginTrans(dbc);
   if (tl>0) then begin
    DbcLog(Now,Format('Begin Transaction level %d.',[tl]));
    DbcHandleErrors;
   end else begin
    DbcLogFail(Now,'Fail on Begin Transaction.');
    DbcHandleErrors;
    Exit;
   end;
   DbcLog(Now,Format('Execute: %s',[cmd]));
   rst:=db_execute(dbc,cmd,adCmdText).ref;
   try
    if (rst<>0) then begin
     numrow:=0; tabs:='';
     numcol:=db_fieldscount(rst);
     DbcLog(Now,Format('Executed. %d field(s) table received.',[numcol]));
     if (numcol=1) then begin
      while not db_eof(rst) do begin
       inc(numrow);
       for ic:=0 to numcol-1 do begin
        id:=db_fieldsNames(rst,ic); // Field Name
        tp:=db_fieldsTypes(rst,id); // Field Type
        sv:=db_fieldsAsString(rst,id,'r',''); // Field Value
        sv:=Trim(sv); tabs:=tabs+EOL+sv;
        if (tp>=0) then
        ComboBoxDbcReadTablesCheckAddTable(sv);
       end;
       if (numrow=MaxRecords) then break;
       if not db_movenext(rst) then break;
      end;
      DbcLog(Now,Format('%d row(s) read.',[numrow]));
      tabs:=StringReplace(SysUtils.Trim(tabs),EOL,',',[rfReplaceAll]);
      if (tabs<>'') then DbcLog(Now,Format('ReadTables: %s',[tabs]));
     end else begin
      DbcLog(Now,'Error: ReadTable expected 1 field table.');
      Exit;
     end;
     if db_commitTrans(dbc) then begin
      DbcLog(Now,'Commit Transaction.');
      tl:=0;
     end else begin
      DbcLogFail(Now,'Fail on Commit Transaction.');
     end;
    end else begin
     DbcLogFail(Now,'Could not execute SQL Query.');
     DbcHandleErrors;
    end;
    ComboBoxDbcReadTablesSortAndSelect;
   finally
    if (tl>0) then begin
     if db_rollbackTrans(dbc)
     then DbcLog(Now,'Rollback Transaction.')
     else DbcLogFail(Now,'Fail on Rollback Transaction.');
    end;
    DbcHandleErrors;
    db_free(rst);
   end;
  end else begin
   DbcLog(Now,'Could not find SQL query for ReadTables.');
  end;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcReadTablesExecute');
 end;
end;

procedure TFormDatabaseBrowser.DbcReadTablesClear;
begin
 ComboBoxDbcReadTables.Items.Clear;
end;

procedure TFormDatabaseBrowser.DbcReadTablesChanged;
const idTable='${TABLE}';
var tab,cmd:LongString;
begin
 if (Self=nil) then Exit; if (dbc=0) then Exit;
 tab:=Trim(ComboBoxDbcReadTables.Text); if (tab='') then Exit;
 if not IsLexeme(tab,lex_sqlname) then tab:=AnsiQuotedStr(tab,QuoteMark);
 if (Pos(idTable,AnsiUpperCase(MemoDbcSqlQuery.Text))>0)
 then cmd:=StringReplace(MemoDbcSqlQuery.Text,idTable,tab,[rfReplaceAll,rfIgnoreCase])
 else cmd:=Format('select * from %s;',[tab]);
 MemoDbcSqlQuery.Text:=cmd;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcReadTablesChange(Sender: TObject);
begin
 DbcReadTablesChanged;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultNiceViewExecute(Sender: TObject);
var minwid,maxwid,collen,colwid,row,col:Integer; cell,patt:LongString;
begin
 try
  minwid:=SpinEditDbcSqlResultMinWidth.Value;
  maxwid:=SpinEditDbcSqlResultMaxWidth.Value;
  for col:=0 to StringGridDbcSqlResult.ColCount-1 do begin
   collen:=1;
   for row:=0 to StringGridDbcSqlResult.RowCount-1 do begin
    cell:=StringGridDbcSqlResult.Cells[col,row];
    collen:=Max(collen,Length(cell)+1);
   end;
   patt:=StringOfChar('M',collen);
   colwid:=Canvas.TextWidth(patt);
   colwid:=Max(Min(colwid,maxwid),minwid);
   StringGridDbcSqlResult.ColWidths[col]:=colwid;
  end;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlResultNiceViewExecute');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcPwdCryptCopyExecute(Sender: TObject);
var key:LongString; mode:Integer;
begin
 key:=Trim(EditDbcPwdCryptResult.Text);
 mode:=ComboBoxDbcPwdCryptMode.ItemIndex;
 if (key<>'') then ClipBoard.AsText:=key else Exit;
 DbcLog(Now,Format('Encrypted password (mode %d) copied to Clipboard: %s',[mode,key]));
 //DbcLog(Now,'You can use it in db_build_connectionstring calls.');
end;

procedure TFormDatabaseBrowser.ActionDbcMenuTablesExecute(Sender: TObject);
var Cap,Tit,Menu,Opt:LongString; Num:Integer; p:TPoint;
begin
 try
  if (ComboBoxDbcReadTables.Items.Count=0) then Exit;
  p:=BitBtnDbcReadTables.ClientToScreen(Point(0,BitBtnDbcReadTables.Height));
  Opt:=Format('@set Form.Left %d relative Screen',[p.x])+EOL
      +Format('@set Form.Top  %d relative Screen',[p.y]);
  Cap:='Table Selector'; Tit:='Select a Table for work:';
  Menu:=ComboBoxDbcReadTables.Items.Text;
  Num:=ComboBoxDbcReadTables.ItemIndex;
  Num:=ListBoxMenu(Cap,Tit,Menu,Num,Opt);
  if (Num<0) then Exit else ComboBoxDbcReadTables.ItemIndex:=Num;
  DbcReadTablesChanged;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcMenuTablesExecute');
 end;
end;

procedure TFormDatabaseBrowser.DbcSqlResultSaveAs(aFileName:LongString);
var Lines:TStringList; Sep:Char;
begin
 if (Self=nil) then Exit;
 aFileName:=SysUtils.Trim(aFileName);
 if (aFileName='') then Exit;
 if FileExists(aFileName) then
 if (YesNo('File is already exists:'+EOL+aFileName+EOL+'Overwrite this file?')<>mrYes) then Exit;
 try
  Lines:=TStringList.Create;
  try
   if SameText(ExtractFileExt(aFileName),'.csv') then sep:=',' else sep:=#9;
   Lines.Text:=DbcSqlResultSelText(StringGridDbcSqlResult.Selection,Sep);
   Lines.SaveToFile(aFileName); DbcLog(Now,'File Saved: '+aFileName);
  finally
   Lines.Free;
  end;
 except
  on E:Exception do BugReport(E,Self,'DbcSqlResultSaveAs');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultSaveAsExecute(Sender: TObject);
begin
 try
  if (SaveDialogDbcSqlResults.InitialDir='')
  then SaveDialogDbcSqlResults.InitialDir:=OpenDialogDbcDatabase.InitialDir;
  if (SaveDialogDbcSqlResults.FileName='')
  then SaveDialogDbcSqlResults.FileName:='NewTable.txt';
  if not SaveDialogDbcSqlResults.Execute then Exit;
  DbcSqlResultSaveAs(SaveDialogDbcSqlResults.FileName);
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlResultSaveAsExecute');
 end;
end;

procedure TFormDatabaseBrowser.ActionDbcSqlResultBlobViewExecute(Sender: TObject);
var g:TStringGrid; r,c,t,m:Integer; s,e,f,b,a,x,dm:LongString;
begin
 if (db_type(dbc)=db_type_connection) and db_active(dbc) then
 try
  a:='';e:='';f:='';a:='';
  g:=StringGridDbcSqlResult;
  r:=g.Selection.Top; c:=g.Selection.Left;
  if not InRange(c,0,g.ColCount-1) then Exit;
  if not InRange(r,0,g.RowCount-1) then Exit;
  t:=DbCon.StringToFieldTypeCode(g.Cells[c,0]);
  if not DbCon.IsBlobFieldTypeCode(t,db_engineid(dbc)) then Exit;
  s:=g.Cells[c,r]; m:=ComboBoxDbcBlobViewMode.ItemIndex;
  dm:=ComboBoxDbcEmptyDataMarker.Items.Text;
  if (s='') or (WordIndex(s,dm,ScanSpaces)>0) then begin
   DbcLog(Now,'Unable to view BLOB: empty data.');
   Exit;
  end;
  if StartsStr('BLOB/',s) or not (m in [3,4,5]) then begin
   DbcLog(Now,'Unable to view BLOB: view mode must be B64/HEX/PCT.');
   Exit;
  end;
  case m of
   3: s:=base64_decode(s);
   4: s:=hex_decode(s);
   5: s:=percent_decode(s);
  end;
  if (s='') then begin
   DbcLog(Now,'Unable to view BLOB: decoding failed.');
   Exit;
  end;
  e:=DbCon.DetectBlobImageType(s);
  if (e='') and not CheckBoxDbcBlobViewDump.Checked then begin
   DbcLog(Now,'Unable to view BLOB: format not detected.');
   Exit;
  end;
  f:=SessionManager.SystemTmpFile('db_browser_blob.'+e);  FileErase(f);
  b:=SessionManager.SystemTmpFile('db_browser_blob.bin'); FileErase(b);
  x:=SessionManager.SystemTmpFile('db_browser_blob.txt'); FileErase(x);
  if CheckBoxDbcBlobViewDump.Checked then begin
   if (WriteBufferToFile(b,s)<>Length(s)) then begin
    DbcLog(Now,'Unable to view BLOB: file write failed.');
    Exit;
   end;
   if not FileIsReadable(b) then begin
    DbcLog(Now,'Unable to view BLOB: unreadable bin file.');
    Exit;
   end;
   if IsUnix then begin
    if not RunCommand('xxd '+QArg(b)+' '+QArg(x),a) then begin
     DbcLog(Now,'Unable to view BLOB: xxd fail.');
     Exit;
    end;
    if not RunCommand('unix grun lister '+QArg(x),a) then begin
     DbcLog(Now,'Unable to view BLOB: lister fail.');
     Exit;
    end;
   end;
   if IsWindows then begin
    if not RunCommand('grun -7w unix xxd '+QArg(b)+' '+QArg(x),a) then begin
     DbcLog(Now,'Unable to view BLOB: xxd fail.');
     Exit;
    end;
    if not RunCommand('grun -7 unix grun lister '+QArg(x),a) then begin
     DbcLog(Now,'Unable to view BLOB: lister fail.');
     Exit;
    end;
   end;
  end else begin
   if (WriteBufferToFile(f,s)<>Length(s)) then begin
    DbcLog(Now,'Unable to view BLOB: file write failed.');
    Exit;
   end;
   if not FileIsReadable(f) then begin
    DbcLog(Now,'Unable to view BLOB: unreadable '+e+' file.');
    Exit;
   end;
   if IsUnix then begin
    if not RunCommand('xdg-open '+QArg(f),a) then begin
     DbcLog(Now,'Unable to view BLOB: xdg-open fail.');
     Exit;
    end;
   end;
   if IsWindows then begin
    if not RunCommand('grun -7 unix grun imagine.exe '+QArg(f),a) then begin
     DbcLog(Now,'Unable to view BLOB: imagine.exe fail.');
     Exit;
    end;
   end;
  end;
 except
  on E:Exception do BugReport(E,Self,'ActionDbcSqlResultBlobViewExecute');
 end;
end;

procedure TFormDatabaseBrowser.ComboBoxDbcEngineChange(Sender: TObject);
begin
 ActionDbcProviderDriverUpdate.Execute;
end;

procedure TFormDatabaseBrowser.BitBtnOdbcAd32Click(Sender: TObject);
begin
 if IsWindows then begin
  SendToMainConsole('@silent @run odbcad32.exe'+EOL);
 end;
 if IsUnix then begin
  DbcLog(Now,'ODBC GUI: not supported yet.');
 end;
end;

procedure TFormDatabaseBrowser.ButtonDbcSqlInfoSqlDbClick(Sender: TObject);
var msg:LongString;
begin
 msg:='KnownProviders='+StringReplace(Trim(SqlDbAssistant.KnownProviders),EOL,',',[rfReplaceAll])+EOL
     +'KnownConnTypes='+StringReplace(Trim(SqlDbAssistant.KnownConnTypes),EOL,',',[rfReplaceAll])+EOL
     +'GetConnectionListAsText:'+EOL
     +SqlDbAssistant.GetConnectionListAsText;
 DbcLog(Now,'Information on SQLDB engine:'+EOL
           +'==================================================='+EOL
           +msg);
end;

procedure TFormDatabaseBrowser.ButtonDbcSqlInfoConnBriefClick(Sender: TObject);
var lst,msg,par,dat:LongString; i:Integer;
const SkipList='ErrorsClear,BugsClear,TotalBugsClear,GetString,'
              +'Properties,FieldTypeToString';
begin
 if not db_active(dbc) then begin
  DbcLog(Now,'Connection is not Active.');
  Exit;
 end;
 msg:=''; lst:=db_ctrl(dbc,'*');
 for i:=1 to WordCount(lst,ScanSpaces) do begin
  par:=ExtractWord(i,lst,ScanSpaces);
  if (WordIndex(par,SkipList,ScanSpaces)>0) then continue;
  dat:=TrimRightChars(db_ctrl(dbc,par),EolnDelims);
  if HasChars(dat,EOL) then dat:=StringReplace(dat,EOL,';',[rfReplaceAll]);
  msg:=msg+par+'='+dat+EOL;
 end;
 msg:=SortTextLines(msg);
 DbcLog(Now,'Brief Information on Connection:'+EOL
           +'======================================================='+EOL
           +msg);
end;

function HidePwd(dbc:Integer; msg:LongString):LongString;
const pattern='(?im-g)^Connection\.Password\s*=.*$';
var pwd,pcm,pck,pci:LongString; mode:Integer;
begin
 Result:=msg;
 pwd:=CookieScan(msg,'Connection.Password',0);
 if (pwd<>'') then begin
  pci:=db_ctrl(dbc,'PwdCryptIv');
  pck:=db_ctrl(dbc,'PwdCryptKey');
  pcm:=db_ctrl(dbc,'PwdCryptMode');
  mode:=StrToIntDef(pcm,pem_OFF);
  if (mode in pem_Secure) then begin
   pwd:=TDbEngineAssistant.EncryptPassword(pwd,mode,pck,pci);
   Result:=SafeReplaceRegExpr(pattern,msg,'Connection.Password='+pwd);
  end;
 end;
end;

procedure TFormDatabaseBrowser.ButtonDbcSqlInfoConnPropsClick(Sender: TObject);
var msg:LongString;
begin
 if not db_active(dbc) then begin
  DbcLog(Now,'Connection is not Active.');
  Exit;
 end;
 msg:=db_ctrl(dbc,'Properties'); msg:=HidePwd(dbc,msg);
 DbcLog(Now,'Information on Connection Properties:'+EOL
           +'============================================================'+EOL
           +msg);
end;

///////////////////////////////////////
// Unit initialization and finalization
///////////////////////////////////////

procedure Init_form_databasebrowser;
begin
 if IsWindows then DatabaseBrowserDefaultOdbcDriver:='SQLite3 ODBC Driver';
end;

procedure Free_form_databasebrowser;
begin
end;

initialization

 Init_form_databasebrowser;

finalization

 Free_form_databasebrowser;

end.

//////////////
// END OF FILE
//////////////

