From f9cd3e94a6cf9179a5c5fb09dc01dbf4ff7d8ea2 Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Mon, 24 Feb 2025 20:37:38 +0100 Subject: [PATCH] Issue #1482: first attempt of a code conversion to Lazarus/FPC, with a small bunch of 5 units (out of 50), and most code commented out --- .gitignore | 30 + heidisql.lpi | 121 + heidisql.lpr | 74 + out/libmariadb.dll | Bin 0 -> 1206272 bytes source/apphelpers.pas | 4610 ++++++++++ source/const.inc | 106 + source/dbconnection.pas | 37 + source/dbstructures.mysql.pas | 3389 +++++++ source/dbstructures.pas | 218 + source/main.lfm | 1171 +++ source/main.pas | 15100 ++++++++++++++++++++++++++++++++ 11 files changed, 24856 insertions(+) create mode 100644 .gitignore create mode 100644 heidisql.lpi create mode 100644 heidisql.lpr create mode 100644 out/libmariadb.dll create mode 100644 source/apphelpers.pas create mode 100644 source/const.inc create mode 100644 source/dbconnection.pas create mode 100644 source/dbstructures.mysql.pas create mode 100644 source/dbstructures.pas create mode 100644 source/main.lfm create mode 100644 source/main.pas diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c90cc388 --- /dev/null +++ b/.gitignore @@ -0,0 +1,30 @@ +# Lazarus compiler-generated binaries (safe to delete) +*.exe +#*.dll +#*.so +*.dylib +*.lrs +*.res +*.compiled +*.dbg +*.ppu +*.o +*.or +*.a + +# Lazarus autogenerated files (duplicated info) +*.rst +*.rsj +*.lrt + +# Lazarus local files (user-specific info) +*.lps + +# Lazarus backups and unit output folders. +# These can be changed by user in Lazarus/project options. +backup/ +*.bak +lib/ + +# Application bundle for Mac OS +*.app/ \ No newline at end of file diff --git a/heidisql.lpi b/heidisql.lpi new file mode 100644 index 00000000..e7ef9f3e --- /dev/null +++ b/heidisql.lpi @@ -0,0 +1,121 @@ + + + + + + + + + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <VersionInfo> + <UseVersionInfo Value="True"/> + <MajorVersionNr Value="12"/> + <MinorVersionNr Value="10"/> + </VersionInfo> + <BuildModes> + <Item Name="Default" Default="True"/> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages> + <Item> + <PackageName Value="laz.virtualtreeview_package"/> + </Item> + <Item> + <PackageName Value="SynEditDsgn"/> + </Item> + <Item> + <PackageName Value="SynEdit"/> + </Item> + <Item> + <PackageName Value="LCL"/> + </Item> + </RequiredPackages> + <Units> + <Unit> + <Filename Value="heidisql.lpr"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="source/main.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + </Unit> + <Unit> + <Filename Value="source/apphelpers.pas"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="source/dbconnection.pas"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="source/dbstructures.pas"/> + <IsPartOfProject Value="True"/> + </Unit> + <Unit> + <Filename Value="source/dbstructures.mysql.pas"/> + <IsPartOfProject Value="True"/> + </Unit> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <PathDelim Value="\"/> + <Target> + <Filename Value="out\heidisql"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <OtherUnitFiles Value="source"/> + <UnitOutputDirectory Value="lib\$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Parsing> + <SyntaxOptions> + <SyntaxMode Value="Delphi"/> + </SyntaxOptions> + </Parsing> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf3"/> + </Debugging> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + <Other> + <CompilerMessages> + <IgnoredMessages idx3177="True" idx3175="True"/> + </CompilerMessages> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions> + <Item> + <Name Value="EAbort"/> + </Item> + <Item> + <Name Value="ECodetoolError"/> + </Item> + <Item> + <Name Value="EFOpenError"/> + </Item> + </Exceptions> + </Debugging> +</CONFIG> diff --git a/heidisql.lpr b/heidisql.lpr new file mode 100644 index 00000000..f3a4088f --- /dev/null +++ b/heidisql.lpr @@ -0,0 +1,74 @@ +program heidisql; + +{$mode delphi}{$H+} + +uses + {$IFDEF UNIX} + cthreads, + {$ENDIF} + {$IFDEF HASAMIGA} + athreads, + {$ENDIF} + Interfaces, // this includes the LCL widgetset + SysUtils, + Forms, main, + { you can add units after this } + apphelpers, + dbconnection, + //gnugettext + dbstructures, + dbstructures.mysql + ; + +{$R *.res} +{.$R resources.rc} + +begin + PostponedLogItems := TDBLogItems.Create(True); + //Application.MainFormOnTaskBar := True; + + // Use MySQL standard format for date/time variables: YYYY-MM-DD HH:MM:SS + // Be aware that Delphi internally converts the slashes in ShortDateFormat to the DateSeparator + DefaultFormatSettings.DateSeparator := '-'; + DefaultFormatSettings.TimeSeparator := ':'; + DefaultFormatSettings.ShortDateFormat := 'yyyy/mm/dd'; + DefaultFormatSettings.LongTimeFormat := 'hh:nn:ss'; + + //AppSettings := TAppSettings.Create; + //SecondInstMsgId := RegisterWindowMessage(APPNAME); + if false then begin // (not AppSettings.ReadBool(asAllowMultipleInstances)) and CheckForSecondInstance then begin + //AppSettings.Free; + Application.Terminate; + end else begin + + {AppLanguage := AppSettings.ReadString(asAppLanguage); + // SysLanguage may be zh_CN, while we don't offer such a language, but anyway, this is just the current system language: + SysLanguage := gnugettext.DefaultInstance.GetCurrentLocaleName; + gnugettext.UseLanguage(AppLanguage); + // First time translation via dxgettext. + // Issue #3064: Ignore TFont, so "Default" on mainform for WinXP users does not get broken. + gnugettext.TP_GlobalIgnoreClass(TFont); + + // Enable padding in customized tooltips + HintWindowClass := TExtHintWindow;} + + RequireDerivedFormResource:=True; + Application.Scaled:=True; + Application.Initialize; + //Application.UpdateFormatSettings := False; + + // Try to set style name. If that fails, the user gets an error message box - reset it to default when that happened + {WantedStyle := AppSettings.ReadString(asTheme); + TStyleManager.TrySetStyle(WantedStyle); + if TStyleManager.ActiveStyle.Name <> WantedStyle then begin + AppSettings.WriteString(asTheme, TStyleManager.ActiveStyle.Name); + end;} + Application.CreateForm(TMainForm, MainForm); + MainForm.AfterFormCreate; + //Application.OnDeactivate := MainForm.ApplicationDeActivate; + //Application.OnShowHint := MainForm.ApplicationShowHint; + //Application.MainFormOnTaskBar := True; + Application.Run; + end; +end. + diff --git a/out/libmariadb.dll b/out/libmariadb.dll new file mode 100644 index 0000000000000000000000000000000000000000..fa87599f04503a8c6a47e703c6232359d77d82fe GIT binary patch literal 1206272 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℞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 NHv)4b@ZU57{|6EILSp~` literal 0 HcmV?d00001 diff --git a/source/apphelpers.pas b/source/apphelpers.pas new file mode 100644 index 00000000..f30c817d --- /dev/null +++ b/source/apphelpers.pas @@ -0,0 +1,4610 @@ +unit apphelpers; + +{$mode delphi}{$H+} + +interface + +uses + Classes, SysUtils, + dbconnection; + +type + + {TSortItemOrder = (sioAscending, sioDescending); + TSortItem = class(TPersistent) + public + Column: String; + Order: TSortItemOrder; + procedure Assign(Source: TPersistent); override; + end; + TSortItems = class(TObjectList<TSortItem>) + public + function AddNew(Column: String=''; Order: TSortItemOrder=sioAscending): TSortItem; + function ComposeOrderClause(Connection: TDBConnection): String; + function FindByColumn(Column: String): TSortItem; + procedure Assign(Source: TSortItems); + end;} + + TLineBreaks = (lbsNone, lbsWindows, lbsUnix, lbsMac, lbsWide, lbsMixed); + + {TUTF8NoBOMEncoding = class(TUTF8Encoding) + public + function GetPreamble: TBytes; override; + end;} + + {TDBObjectEditor = class(TFrame) + private + FModified: Boolean; + procedure SetModified(Value: Boolean); + protected + FMainSynMemo: TSynMemo; // Main editor in case of routine, view, trigger or event + FMainSynMemoPreviousTopLine: Integer; + function ObjectExists: Boolean; + public + DBObject: TDBObject; + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + procedure Init(Obj: TDBObject); virtual; + function DeInit: TModalResult; virtual; + property Modified: Boolean read FModified write SetModified; + function ApplyModifications: TModalResult; virtual; abstract; + end; + TDBObjectEditorClass = class of TDBObjectEditor;} + + {TSQLBatch = class; + TSQLSentence = class(TObject) + private + FOwner: TSQLBatch; + function GetSize: Integer; + function GetSQL: String; + function GetSQLWithoutComments: String; + public + LeftOffset, RightOffset: Integer; + constructor Create(Owner: TSQLBatch); + property SQL: String read GetSQL; + property SQLWithoutComments: String read GetSQLWithoutComments; + property Size: Integer read GetSize; + end; + TSQLBatch = class(TObjectList<TSQLSentence>) + private + FSQL: String; + procedure SetSQL(Value: String); + function GetSize: Integer; + function GetSQLWithoutComments: String; overload; + public + class function GetSQLWithoutComments(FullSQL: String): String; overload; + property Size: Integer read GetSize; + property SQL: String read FSQL write SetSQL; + property SQLWithoutComments: String read GetSQLWithoutComments; + end; } + + // Download + {THttpDownload = class(TObject) + private + FOwner: TComponent; + FURL: String; + FLastContent: String; + FBytesRead: Integer; + FContentLength: Integer; + FTimeOut: Cardinal; + FOnProgress: TNotifyEvent; + public + constructor Create(Owner: TComponent); + procedure SendRequest(Filename: String); + property OnProgress: TNotifyEvent read FOnProgress write FOnProgress; + property URL: String read FURL write FURL; + property TimeOut: Cardinal read FTimeOut write FTimeOut; + property BytesRead: Integer read FBytesRead; + property ContentLength: Integer read FContentLength; + property LastContent: String read FLastContent; + end; } + + // Extended string list with support for empty values + {TExtStringList = class(TStringList) + private + function GetValue(const Name: string): string; + procedure SetValue(const Name, Value: string); reintroduce; + public + property Values[const Name: string]: string read GetValue write SetValue; + end;} + + // Threading stuff + {TQueryThread = class(TThread) + private + FConnection: TDBConnection; + FBatch: TSQLBatch; + FTabNumber: Integer; + FBatchInOneGo: Boolean; + FStopOnErrors: Boolean; + FAborted: Boolean; + FErrorMessage: String; + FBatchPosition: Integer; + FQueriesInPacket: Integer; + FQueryStartedAt: TDateTime; + FQueryTime: Cardinal; + FQueryNetTime: Cardinal; + FRowsAffected: Int64; + FRowsFound: Int64; + FWarningCount: Int64; + public + property Connection: TDBConnection read FConnection; + property Batch: TSQLBatch read FBatch; + property TabNumber: Integer read FTabNumber; + property BatchPosition: Integer read FBatchPosition; + property QueriesInPacket: Integer read FQueriesInPacket; + property QueryStartedAt: TDateTime read FQueryStartedAt; + property QueryTime: Cardinal read FQueryTime; + property QueryNetTime: Cardinal read FQueryNetTime; + property RowsAffected: Int64 read FRowsAffected; + property RowsFound: Int64 read FRowsFound; + property WarningCount: Int64 read FWarningCount; + property Aborted: Boolean read FAborted write FAborted; + property ErrorMessage: String read FErrorMessage; + constructor Create(Connection: TDBConnection; Batch: TSQLBatch; TabNumber: Integer); + procedure Execute; override; + procedure LogFromThread(Msg: String; Category: TDBLogCategory); + end;} + + {TSqlTranspiler = class(TObject) + class function CreateTable(SQL: String; SourceDb, TargetDb: TDBConnection): String; + end;} + + {TClipboardHelper = class helper for TClipboard + private + function GetTryAsText: String; + procedure SetTryAsText(AValue: String); + public + property TryAsText: String read GetTryAsText write SetTryAsText; + end;} + + {TWinControlHelper = class helper for TWinControl + public + procedure TrySetFocus; + end;} + + //TSimpleKeyValuePairs = TDictionary<String, String>; + + TAppSettingDataType = (adInt, adBool, adString); + TAppSettingIndex = (asHiddenColumns, asFilter, asSort, asDisplayedColumnsSorted, asLastSessions, + asLastActiveSession, asAutoReconnect, asRestoreLastUsedDB, asLastUsedDB, asTreeBackground, asIgnoreDatabasePattern, asLogFileDdl, asLogFileDml, asLogFilePath, + asFontName, asFontSize, asTabWidth, asDataFontName, asDataFontSize, asDataLocalNumberFormat, asLowercaseHex, asHintsOnResultTabs, asHightlightSameTextBackground, + asShowRowId, + asLogsqlnum, asLogsqlwidth, asSessionLogsDirectory, asLogHorizontalScrollbar, asSQLColActiveLine, + asSQLColMatchingBraceForeground, asSQLColMatchingBraceBackground, + asMaxColWidth, asDatagridMaximumRows, asDatagridRowsPerStep, asGridRowLineCount, asColumnHeaderClick, asReuseEditorConfiguration, + asLogToFile, asMainWinMaximized, asMainWinLeft, asMainWinTop, asMainWinWidth, + asMainWinHeight, asMainWinOnMonitor, asCoolBandIndex, asCoolBandBreak, asCoolBandWidth, asToolbarShowCaptions, asQuerymemoheight, asDbtreewidth, + asDataPreviewHeight, asDataPreviewEnabled, asLogHeight, asQueryhelperswidth, asStopOnErrorsInBatchMode, + asWrapLongLines, asCodeFolding, asDisplayBLOBsAsText, asSingleQueries, asMemoEditorWidth, asMemoEditorHeight, asMemoEditorMaximized, + asMemoEditorWrap, asMemoEditorHighlighter, asMemoEditorAlwaysFormatCode, asDelimiter, asSQLHelpWindowLeft, asSQLHelpWindowTop, asSQLHelpWindowWidth, + asSQLHelpWindowHeight, asSQLHelpPnlLeftWidth, asSQLHelpPnlRightTopHeight, asHost, + asUser, asPassword, asCleartextPluginEnabled, asWindowsAuth, asLoginPrompt, asPort, asLibrary, asAllProviders, + asSSHtunnelActive, asPlinkExecutable, asSshExecutable, asSSHtunnelHost, asSSHtunnelHostPort, asSSHtunnelPort, asSSHtunnelUser, + asSSHtunnelPassword, asSSHtunnelTimeout, asSSHtunnelPrivateKey, asSSLActive, asSSLKey, + asSSLCert, asSSLCA, asSSLCipher, asSSLVerification, asSSLWarnUnused, asNetType, asCompressed, asLocalTimeZone, asQueryTimeout, asKeepAlive, + asStartupScriptFilename, asDatabases, asComment, asDatabaseFilter, asTableFilter, asFilterVT, asExportSQLCreateDatabases, + asExportSQLCreateTables, asExportSQLDataHow, asExportSQLDataInsertSize, asExportSQLFilenames, asExportZIPFilenames, asExportSQLDirectories, + asExportSQLDatabase, asExportSQLServerDatabase, asExportSQLOutput, asExportSQLAddComments, asExportSQLRemoveAutoIncrement, asExportSQLRemoveDefiner, + asGridExportWindowWidth, asGridExportWindowHeight, asGridExportOutputCopy, asGridExportOutputFile, + asGridExportFilename, asGridExportRecentFiles, asGridExportEncoding, asGridExportFormat, asGridExportSelection, + asGridExportColumnNames, asGridExportIncludeAutoInc, asGridExportIncludeQuery, asGridExportRemoveLinebreaks, + asGridExportSeparator, asGridExportEncloser, asGridExportTerminator, asGridExportNull, + + asGridExportClpColumnNames, asGridExportClpIncludeAutoInc, asGridExportClpRemoveLinebreaks, + asGridExportClpSeparator, asGridExportClpEncloser, asGridExportClpTerminator, asGridExportClpNull, + + asCSVImportSeparator, asCSVImportEncloser, asCSVImportTerminator, asCSVImportFieldEscaper, asCSVImportWindowWidth, asCSVImportWindowHeight, + asCSVImportFilename, asCSVImportFieldsEnclosedOptionally, asCSVImportIgnoreLines, asCSVImportLowPriority, asCSVImportLocalNumbers, + asCSVImportDuplicateHandling, asCSVImportParseMethod, asCSVKeepDialogOpen, + asUpdatecheck, asUpdatecheckBuilds, asUpdatecheckInterval, asUpdatecheckLastrun, asUpdateCheckWindowWidth, asUpdateCheckWindowHeight, + asTableToolsWindowWidth, asTableToolsWindowHeight, asTableToolsTreeWidth, + asTableToolsFindTextTab, asTableToolsFindText, asTableToolsFindSQL, asTableToolsDatatype, asTableToolsFindCaseSensitive, asTableToolsFindMatchType, asFileImportWindowWidth, asFileImportWindowHeight, + asEditVarWindowWidth, asEditVarWindowHeight, asUsermanagerWindowWidth, asUsermanagerWindowHeight, asUsermanagerListWidth, + asSelectDBOWindowWidth, asSelectDBOWindowHeight, + asSessionManagerListWidth, asSessionManagerWindowWidth, asSessionManagerWindowHeight, asSessionManagerWindowLeft, asSessionManagerWindowTop, + asCopyTableWindowHeight, asCopyTableWindowWidth, asCopyTableColumns, asCopyTableKeys, asCopyTableForeignKeys, + asCopyTableData, asCopyTableRecentFilter, asServerVersion, asServerVersionFull, asLastConnect, + asConnectCount, asRefusedCount, asSessionCreated, asDoUsageStatistics, + asLastUsageStatisticCall, asWheelZoom, asDisplayBars, asMySQLBinaries, asCustomSnippetsDirectory, + asPromptSaveFileOnTabClose, asRestoreTabs, asTabCloseOnDoubleClick, asTabCloseOnMiddleClick, asTabsInMultipleLines, asTabIconsGrayscaleMode, + asWarnUnsafeUpdates, asQueryGridLongSortRowNum, + asCompletionProposal, asCompletionProposalInterval, asCompletionProposalSearchOnMid, asCompletionProposalWidth, asCompletionProposalNbLinesInWindow, asAutoUppercase, + asTabsToSpaces, asFilterPanel, asAllowMultipleInstances, asFindDialogSearchHistory, asGUIFontName, asGUIFontSize, + asTheme, asIconPack, asWebSearchBaseUrl, + asFindDialogReplaceHistory, asMaxQueryResults, asLogErrors, + asLogUserSQL, asLogSQL, asLogInfos, asLogDebug, asLogScript, asLogTimestamp, asFieldColorNumeric, + asFieldColorReal, asFieldColorText, asFieldColorBinary, asFieldColorDatetime, asFieldColorSpatial, + asFieldColorOther, asFieldEditorBinary, asFieldEditorDatetime, asFieldEditorDatetimePrefill, asFieldEditorEnum, + asFieldEditorSet, asFieldNullBackground, asRowBackgroundEven, asRowBackgroundOdd, asGroupTreeObjects, asDisplayObjectSizeColumn, asSQLfile, + asActionShortcut1, asActionShortcut2, asHighlighterForeground, asHighlighterBackground, asHighlighterStyle, + asListColWidths, asListColsVisible, asListColPositions, asListColSort, asSessionFolder, + asRecentFilter, asTimestampColumns, asDateTimeEditorCursorPos, asAppLanguage, asAutoExpand, asDoubleClickInsertsNodeText, asForeignDropDown, + asIncrementalSearch, asQueryHistoryEnabled, asQueryHistoryKeepDays, + asColumnSelectorWidth, asColumnSelectorHeight, asDonatedEmail, asFavoriteObjects, asFavoriteObjectsOnly, asFullTableStatus, asLineBreakStyle, + asPreferencesWindowWidth, asPreferencesWindowHeight, + asFileDialogEncoding, + asThemePreviewWidth, asThemePreviewHeight, asThemePreviewTop, asThemePreviewLeft, + asCreateDbCollation, asRealTrailingZeros, + asSequalSuggestWindowWidth, asSequalSuggestWindowHeight, asSequalSuggestPrompt, asSequalSuggestRecentPrompts, + asReformatter, asReformatterNoDialog, asAlwaysGenerateFilter, + asGenerateDataNumRows, asGenerateDataNullAmount, asWebOnceAction, + asUnused); + TAppSetting = record + Name: String; + Session: Boolean; + DefaultInt, CurrentInt: Integer; + DefaultBool, CurrentBool: Boolean; + DefaultString, CurrentString: String; + Synced: Boolean; + end; + {TAppSettings = class(TObject) + private + FReads, FWrites: Integer; + FBasePath: String; + FSessionPath: String; + FStoredPath: String; + FRegistry: TRegistry; + FPortableMode: Boolean; + FPortableModeReadOnly: Boolean; + FRestoreTabsInitValue: Boolean; + FSettingsFile: String; + FSettings: Array[TAppSettingIndex] of TAppSetting; + const FPortableLockFileBase: String='portable.lock'; + procedure InitSetting(Index: TAppSettingIndex; Name: String; + DefaultInt: Integer=0; DefaultBool: Boolean=False; DefaultString: String=''; + Session: Boolean=False); + procedure SetSessionPath(Value: String); + procedure PrepareRegistry; + procedure Read(Index: TAppSettingIndex; FormatName: String; + DataType: TAppSettingDataType; var I: Integer; var B: Boolean; var S: String; + DI: Integer; DB: Boolean; DS: String); + procedure Write(Index: TAppSettingIndex; FormatName: String; + DataType: TAppSettingDataType; I: Integer; B: Boolean; S: String); + public + constructor Create; + destructor Destroy; override; + function ReadInt(Index: TAppSettingIndex; FormatName: String=''; Default: Integer=0): Integer; + function ReadIntDpiAware(Index: TAppSettingIndex; AControl: TControl; FormatName: String=''; Default: Integer=0): Integer; + function ReadBool(Index: TAppSettingIndex; FormatName: String=''; Default: Boolean=False): Boolean; + function ReadString(Index: TAppSettingIndex; FormatName: String=''; Default: String=''): String; overload; + function ReadString(ValueName: String): String; overload; + procedure WriteInt(Index: TAppSettingIndex; Value: Integer; FormatName: String=''); + procedure WriteIntDpiAware(Index: TAppSettingIndex; AControl: TControl; Value: Integer; FormatName: String=''); + procedure WriteBool(Index: TAppSettingIndex; Value: Boolean; FormatName: String=''); + procedure WriteString(Index: TAppSettingIndex; Value: String; FormatName: String=''); overload; + procedure WriteString(ValueName, Value: String); overload; + function GetDefaultInt(Index: TAppSettingIndex): Integer; + function GetDefaultBool(Index: TAppSettingIndex): Boolean; + function GetDefaultString(Index: TAppSettingIndex): String; + function GetValueName(Index: TAppSettingIndex): String; + function GetValueNames: TStringList; + function GetKeyNames: TStringList; + function GetSessionNames(ParentPath: String; var Folders: TStringList): TStringList; + procedure GetSessionPaths(ParentPath: String; var Sessions: TStringList); + function DeleteValue(Index: TAppSettingIndex; FormatName: String=''): Boolean; overload; + function DeleteValue(ValueName: String): Boolean; overload; + procedure DeleteCurrentKey; + procedure MoveCurrentKey(TargetPath: String); + function ValueExists(Index: TAppSettingIndex): Boolean; + function SessionPathExists(SessionPath: String): Boolean; + function IsEmptyKey: Boolean; + procedure ResetPath; + procedure StorePath; + procedure RestorePath; + property SessionPath: String read FSessionPath write SetSessionPath; + property PortableMode: Boolean read FPortableMode; + property PortableModeReadOnly: Boolean read FPortableModeReadOnly write FPortableModeReadOnly; + property Writes: Integer read FWrites; + procedure ImportSettings(Filename: String); + function ExportSettings(Filename: String): Boolean; overload; + function ExportSettings: Boolean; overload; + // Common directories + function DirnameUserAppData: String; + function DirnameUserDocuments: String; + function DirnameSnippets: String; + function DirnameBackups: String; + function DirnameHighlighters: String; + // "Static" options, initialized in OnCreate only. For settings which need a restart to take effect. + property RestoreTabsInitValue: Boolean read FRestoreTabsInitValue; + end;} + +{$I const.inc} + + function Implode(Separator: String; a: TStrings): String; + function Explode(Separator, Text: String) :TStringList; + //procedure ExplodeQuotedList(Text: String; var List: TStringList); + //function StrEllipsis(const S: String; MaxLen: Integer; FromLeft: Boolean=True): String; + //function encrypt(str: String): String; + //function decrypt(str: String): String; + //function HTMLSpecialChars(str: String): String; + //function EncodeURLParam(const Value: String): String; + //procedure StreamWrite(S: TStream; Text: String = ''); + //function _GetFileSize(Filename: String): Int64; + //function DeleteFileWithUndo(sFileName: String): Boolean; + //function MakeInt(Str: String) : Int64; + //function MakeFloat(Str: String): Extended; + //function RoundCommercial(e: Extended): Int64; + //function CleanupNumber(Str: String): String; + //function IsInt(Str: String): Boolean; + //function IsFloat(Str: String): Boolean; + //function ScanLineBreaks(Text: String): TLineBreaks; + //function fixNewlines(txt: String): String; + //procedure StripNewLines(var txt: String; Replacement: String=' '); + //function GetLineBreak(LineBreakIndex: TLineBreaks): String; + //procedure RemoveNullChars(var Text: String; var HasNulls: Boolean); + //function GetShellFolder(FolderId: TGUID): String; + //function ValidFilename(Str: String): String; + //function FormatNumber( str: String; Thousands: Boolean=True): String; Overload; + //function UnformatNumber(Val: String): String; + //function FormatNumber( int: Int64; Thousands: Boolean=True): String; Overload; + //function FormatNumber( flt: Double; decimals: Integer = 0; Thousands: Boolean=True): String; Overload; + //procedure ShellExec(cmd: String; path: String=''; params: String=''; RunHidden: Boolean=False); + //function getFirstWord(text: String; MustStartWithWordChar: Boolean=True): String; + //function RegExprGetMatch(Expression: String; var Input: String; ReturnMatchNum: Integer; DeleteFromSource, CaseInsensitive: Boolean): String; Overload; + //function RegExprGetMatch(Expression: String; Input: String; ReturnMatchNum: Integer): String; Overload; + //function ExecRegExprI(const ARegExpr, AInputStr: RegExprString): Boolean; + //function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; Overload; + //function FormatByteNumber( Bytes: String; Decimals: Byte = 1 ): String; Overload; + //function FormatTimeNumber(Seconds: Double; DisplaySeconds: Boolean; MilliSecondsPrecision: Integer=1): String; + //function GetTempDir: String; + //procedure SaveUnicodeFile(Filename: String; Text: String; Encoding: TEncoding); + //procedure OpenTextFile(const Filename: String; out Stream: TFileStream; var Encoding: TEncoding); + //function DetectEncoding(Stream: TStream): TEncoding; + //function ReadTextfileChunk(Stream: TFileStream; Encoding: TEncoding; ChunkSize: Int64 = 0): String; + //function ReadTextfile(Filename: String; Encoding: TEncoding): String; + //function ReadBinaryFile(Filename: String; MaxBytes: Int64): AnsiString; + //procedure StreamToClipboard(Text, HTML: TStream); + //function WideHexToBin(text: String): AnsiString; + //function BinToWideHex(bin: AnsiString): String; + //procedure FixVT(VT: TVirtualStringTree; MultiLineCount: Word=1); + //function GetTextHeight(Font: TFont): Integer; + //function ColorAdjustBrightness(Col: TColor; Shift: SmallInt): TColor; + //procedure DeInitializeVTNodes(Sender: TBaseVirtualTree); + //function FindNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode): PVirtualNode; + //function SelectNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode=nil): Boolean; overload; + //function SelectNode(VT: TVirtualStringTree; Node: PVirtualNode; ClearSelection: Boolean=True): Boolean; overload; + //procedure GetVTSelection(VT: TVirtualStringTree; var SelectedCaptions: TStringList; var FocusedCaption: String); + //procedure SetVTSelection(VT: TVirtualStringTree; SelectedCaptions: TStringList; FocusedCaption: String); + //function GetNextNode(Tree: TVirtualStringTree; CurrentNode: PVirtualNode; Selected: Boolean=False): PVirtualNode; + //function GetPreviousNode(Tree: TVirtualStringTree; CurrentNode: PVirtualNode; Selected: Boolean=False): PVirtualNode; + //function DateBackFriendlyCaption(d: TDateTime): String; + //function DateTimeToStrDef(DateTime: TDateTime; Default: String): String; + //function TruncDef(X: Real; Default: Int64): Int64; + //function GetLightness(AColor: TColor): Byte; + //function ParamBlobToStr(lpData: Pointer): String; + //function ParamStrToBlob(out cbData: DWORD): Pointer; + //function CheckForSecondInstance: Boolean; + //function GetParentFormOrFrame(Comp: TWinControl): TWinControl; + //function KeyPressed(Code: Integer): Boolean; + //function GeneratePassword(Len: Integer): String; + //procedure InvalidateVT(VT: TVirtualStringTree; RefreshTag: Integer; ImmediateRepaint: Boolean); + //function CharAtPos(Str: String; Pos: Integer): Char; + //function CompareAnyNode(Text1, Text2: String): Integer; + //function StringListCompareAnythingAsc(List: TStringList; Index1, Index2: Integer): Integer; + //function StringListCompareAnythingDesc(List: TStringList; Index1, Index2: Integer): Integer; + //function StringListCompareByValue(List: TStringList; Index1, Index2: Integer): Integer; + //function StringListCompareByLength(List: TStringList; Index1, Index2: Integer): Integer; + //function GetImageLinkTimeStamp(const FileName: string): TDateTime; + //function IsEmpty(Str: String): Boolean; + //function IsNotEmpty(Str: String): Boolean; + //function MessageDialog(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons): Integer; overload; + //function MessageDialog(const Title, Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; KeepAskingSetting: TAppSettingIndex=asUnused; FooterText: String=''): Integer; overload; + //function ErrorDialog(Msg: string): Integer; overload; + //function ErrorDialog(const Title, Msg: string): Integer; overload; + //function GetLocaleString(const ResourceId: Integer): WideString; + //function GetHTMLCharsetByEncoding(Encoding: TEncoding): String; + //procedure ParseCommandLine(CommandLine: String; var ConnectionParams: TConnectionParameters; var FileNames: TStringList; var RunFrom: String); + function _(const Pattern: string): string; + function f_(const Pattern: string; const Args: array of const): string; + //function GetOutputFilename(FilenameWithPlaceholders: String; DBObj: TDBObject): String; + //function GetOutputFilenamePlaceholders: TStringList; + //function GetSystemImageList: TImageList; + //function GetSystemImageIndex(Filename: String): Integer; + //function GetExecutableBits: Byte; + //procedure Help(Sender: TObject; Anchor: String); + //function PortOpen(Port: Word): Boolean; + //function IsValidFilePath(FilePath: String): Boolean; + //function FileIsWritable(FilePath: String): Boolean; + //function GetProductInfo(dwOSMajorVersion, dwOSMinorVersion, dwSpMajorVersion, dwSpMinorVersion: DWORD; out pdwReturnedProductType: DWORD): BOOL stdcall; external kernel32 delayed; + //function GetCurrentPackageFullName(out Len: Cardinal; Name: PWideChar): Integer; stdcall; external kernel32 delayed; + //function GetThemeColor(Color: TColor): TColor; + //function ThemeIsDark(ThemeName: String=''): Boolean; + //function ProcessExists(pid: Cardinal; ExeNamePattern: String): Boolean; + //procedure ToggleCheckBoxWithoutClick(chk: TCheckBox; State: Boolean); + //function SynCompletionProposalPrettyText(ImageIndex: Integer; LeftText, CenterText, RightText: String; LeftColor: TColor=-1; CenterColor: TColor=-1; RightColor: TColor=-1): String; + //function PopupComponent(Sender: TObject): TComponent; + //function IsWine: Boolean; + //function DirSep: Char; + //procedure FindComponentInstances(BaseForm: TComponent; ClassType: TClass; var List: TObjectList); + //function WebColorStrToColorDef(WebColor: string; Default: TColor): TColor; + //function UserAgent(OwnerComponent: TComponent): String; + //function CodeIndent(Steps: Integer=1): String; + //function EscapeHotkeyPrefix(Text: String): String; + +var + //AppSettings: TAppSettings; + MutexHandle: THandle = 0; + //SystemImageList: TImageList = nil; + //mtCriticalConfirmation: TMsgDlgType = mtCustom; + //ConfirmIcon: TIcon; + NumberChars: TSysCharSet; + LibHandleUser32: THandle; + //UTF8NoBOMEncoding: TUTF8NoBOMEncoding; + DateTimeNever: TDateTime; + IsWineStored: Integer = -1; + +implementation + +uses main; //, extra_controls; + + + +{function WideHexToBin(text: String): AnsiString; +var + buf: AnsiString; +begin + buf := AnsiString(text); + SetLength(Result, Length(text) div 2); + HexToBin(PAnsiChar(buf), @Result[1], Length(Result)); +end;} + +{function BinToWideHex(bin: AnsiString): String; +var + buf: AnsiString; +begin + SetLength(buf, Length(bin) * 2); + BinToHex(@bin[1], PAnsiChar(buf), Length(bin)); + Result := String(buf); +end;} + + +{*** + Convert a TStringList to a string using a separator-string + + @todo Look at each caller to see if escaping is necessary. + @param string Separator + @param a TStringList Containing strings + @return string +} +function Implode(Separator: String; a: TStrings): String; +var + i : Integer; +begin + Result := ''; + for i:=0 to a.Count-1 do + begin + Result := Result + a[i]; + if i < a.Count-1 then + Result := Result + Separator; + end; +end; + + +function Explode(Separator, Text: String): TStringList; +var + i: Integer; + Item: String; +begin + // Explode a string by separator into a TStringList + Result := TStringList.Create; + while true do begin + i := Pos(Separator, Text); + if i = 0 then begin + // Last or only segment: Add to list if it's the last. Add also if it's not empty and list is empty. + // Do not add if list is empty and text is also empty. + if (Result.Count > 0) or (Text <> '') then + Result.Add(Text); + break; + end; + Item := Copy(Text, 1, i-1); + Result.Add(Item); + Delete(Text, 1, i-1+Length(Separator)); + end; +end; + + +{*** + Shorten string to length len and append 3 dots + + @param string String to shorten + @param integer Wished Length of string + @return string +} +{function StrEllipsis(const S: String; MaxLen: Integer; FromLeft: Boolean=True): String; +begin + Result := S; + if Length(Result) <= MaxLen then + Exit; + if FromLeft then begin + SetLength(Result, MaxLen); + Result[MaxLen] := '…'; + end else begin + Result := Copy(Result, Length(Result)-MaxLen, Length(Result)); + Result := '…' + Result; + end; +end;} + + + +{*** + Password-encryption, used to store session-passwords in registry + + @param string Text to encrypt + @return string Encrypted Text +} +{function encrypt(str: String) : String; +var + i, salt, nr : integer; + h : String; +begin + randomize(); + result := ''; + salt := random(9) + 1; + for i:=1 to length(str) do begin + nr := ord(str[i])+salt; + if nr > 255 then + nr := nr - 255; + h := inttohex(nr,0); + if length(h) = 1 then + h := '0' + h; + result := result + h; + end; + result := result + inttostr(salt); +end;} + + + +{*** + Password-decryption, used to restore session-passwords from registry + + @param string Text to decrypt + @return string Decrypted Text +} +{function decrypt(str: String) : String; +var + j, salt, nr : integer; +begin + result := ''; + if str = '' then exit; + j := 1; + salt := StrToIntDef(str[length(str)],0); + result := ''; + while j < length(str)-1 do begin + nr := StrToInt('$' + str[j] + str[j+1]) - salt; + if nr < 0 then + nr := nr + 255; + result := result + chr(nr); + inc(j, 2); + end; +end;} + + +{function HTMLSpecialChars(str: String) : String; +begin + // Convert critical HTML-characters to entities. Used in grid export. + result := StringReplace(str, '&', '&', [rfReplaceAll]); + result := StringReplace(result, '<', '<', [rfReplaceAll]); + result := StringReplace(result, '>', '>', [rfReplaceAll]); +end;} + + +{function EncodeURLParam(const Value: String): String; +var + c: Char; +const} + //UnsafeChars: String = '*<>#%"{}|\^[]`?&+;'; +{begin + // Encode critical chars in url parameter + Result := ''; + for c in Value do begin + if (Pos(c, UnsafeChars)>0) or (Ord(c) < 33) or (Ord(c) > 128) then + Result := Result + '%'+IntToHex(Ord(c), 2) + else + Result := Result + c; + end; +end;} + + +{** + Write some UTF8 text to a file- or memorystream +} +{procedure StreamWrite(S: TStream; Text: String = ''); +var + utf8: AnsiString; +begin + utf8 := Utf8Encode(Text); + S.Write(utf8[1], Length(utf8)); +end;} + + +{*** + Return filesize of a given file + Partly taken from https://www.delphipraxis.net/194137-getfilesize-welches-ist-die-bessere-funktion-2.html + @param string Filename + @return int64 Size in bytes +} +{function _GetFileSize(Filename: String): Int64; +var + Attr: TWin32FileAttributeData; +begin + FillChar(Attr, SizeOf(Attr), 0); + if GetFileAttributesEx(PChar(Filename), GetFileExInfoStandard, @Attr) then + begin + Result := Int64(Attr.nFileSizeHigh) shl 32 + Int64(Attr.nFileSizeLow); + end + else + Result := -1; +end;} + + +{function DeleteFileWithUndo(sFileName: string): Boolean; +var + fos: TSHFileOpStruct; +begin + FillChar(fos, SizeOf(fos), 0); + fos.wFunc := FO_DELETE; + fos.pFrom := PChar(sFileName + #0); + fos.fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_SILENT; + Result := (0 = ShFileOperation(fos)); +end;} + + +{*** + Convert a string-number to an integer-number + + @param string String-number + @return int64 +} +{function MakeInt(Str: String): Int64; +begin + // Result has to be of integer type + try + Result := Trunc(MakeFloat(Str)); + except + Result := 0; + end; +end;} + + +{function CleanupNumber(Str: String): String; +var + i: Integer; + HasDecimalSep: Boolean; +begin + // Ensure the passed string contains a valid number, which is convertable by StrToFloat afterwards + // Return it as string again, as there are callers which need to handle unsigned bigint's somehow - + // there is no unsigned 64 bit integer type in Delphi. + Result := ''; + + // Unformatted float coming in? Detect by order of thousand and decimal char + if ((Pos(',', Str) > 0) and (Pos(',', Str) < Pos('.', Str))) + or ((Pos('.', Str) > 0) and (Pos('.', ReverseString(Str)) <> 4)) + then begin + Str := StringReplace(Str, '.', '*', [rfReplaceAll]); + Str := StringReplace(Str, ',', FormatSettings.ThousandSeparator, [rfReplaceAll]); + Str := StringReplace(Str, '*', FormatSettings.DecimalSeparator, [rfReplaceAll]); + end; + + HasDecimalSep := False; + for i:=1 to Length(Str) do begin + if CharInSet(Str[i], NumberChars) or ((Str[i] = '-') and (Result='')) then + begin + // Avoid confusion and AV in StrToFloat() + if (FormatSettings.ThousandSeparator = FormatSettings.DecimalSeparator) and (Str[i] = FormatSettings.DecimalSeparator) then + continue; + // Ensure only 1 decimalseparator is left + if (Str[i] = FormatSettings.DecimalSeparator) and HasDecimalSep then + continue; + if Str[i] = FormatSettings.DecimalSeparator then + HasDecimalSep := True; + if Str[i] = FormatSettings.ThousandSeparator then + Continue; + Result := Result + Str[i]; + end else + Break; + end; + if (Result = '') or (Result = '-') then + Result := '0'; +end;} + + +{function IsInt(Str: String): Boolean; +begin + Result := IntToStr(MakeInt(Str)) = Str; +end;} + + +{function IsFloat(Str: String): Boolean; +begin + Result := FloatToStr(MakeFloat(Str)) = Str; +end;} + + +{*** + Convert a string-number to an floatingpoint-number + + @param String text representation of a number + @return Extended +} +{function MakeFloat(Str: String): Extended; +var + p_kb, p_mb, p_gb, p_tb, p_pb : Integer; +begin + // Convert result to a floating point value to ensure + // we don't discard decimal digits for the next step + Result := StrToFloat(CleanupNumber(Str)); + + // Detect if the string was previously formatted by FormatByteNumber + // and convert it back by multiplying it with its byte unit + p_kb := Pos(NAME_KB, Str); + p_mb := Pos(NAME_MB, Str); + p_gb := Pos(NAME_GB, Str); + p_tb := Pos(NAME_TB, Str); + p_pb := Pos(NAME_PB, Str); + + if (p_kb > 0) and (p_kb = Length(Str)-Length(NAME_KB)+1) then + Result := Result * SIZE_KB + else if (p_mb > 0) and (p_mb = Length(Str)-Length(NAME_MB)+1) then + Result := Result * SIZE_MB + else if (p_gb > 0) and (p_gb = Length(Str)-Length(NAME_GB)+1) then + Result := Result * SIZE_GB + else if (p_tb > 0) and (p_tb = Length(Str)-Length(NAME_TB)+1) then + Result := Result * SIZE_TB + else if (p_pb > 0) and (p_pb = Length(Str)-Length(NAME_PB)+1) then + Result := Result * SIZE_PB; +end;} + + +{function RoundCommercial(e: Extended): Int64; +begin + // "Kaufmännisch runden" + // In contrast to Delphi's Round() which rounds *.5 to the next even number + Result := Trunc(e); + if Frac(e) >= 0.5 then + Result := Result + 1; +end;} + + +{*** + SynEdit removes all newlines and semi-randomly decides a + new newline format to use for any text edited. + See also: Delphi's incomplete implementation of TTextLineBreakStyle in System.pas + + @param string Text to test + @return TLineBreaks +} +{function ScanLineBreaks(Text: String): TLineBreaks; +var + i, SeekSize: Integer; + c: Char; + procedure SetResult(Style: TLineBreaks); + begin + // Note: Prefer "(foo <> a) and (foo <> b)" over "not (foo in [a, b])" in excessive loops + // for performance reasons - there is or was a Delphi bug leaving those inline SETs in memory + // after usage. Unfortunately can't remember which bug id it was and if it still exists. + if (Result <> lbsNone) and (Result <> Style) then + Result := lbsMixed + else + Result := Style; + end; +begin + Result := lbsNone; + SeekSize := Min(Length(Text), SIZE_MB); + if SeekSize = 0 then + Exit; + i := 1; + repeat + c := Text[i]; + if c = #13 then begin + if (i < SeekSize) and (Text[i+1] = #10) then begin + Inc(i); + SetResult(lbsWindows); + end else + SetResult(lbsMac); + end else if c = LB_UNIX then + SetResult(lbsUnix) + else if c = LB_WIDE then + SetResult(lbsWide); + i := i + 1; + // No need to do more checks after detecting mixed style + if Result = lbsMixed then + break; + until i > SeekSize; +end;} + + +{*** + Unify CR's and LF's to CRLF + + @param string Text to fix + @return string +} +{function fixNewlines(txt: String): String; +begin + txt := StringReplace(txt, CRLF, #10, [rfReplaceAll]); + txt := StringReplace(txt, #13, #10, [rfReplaceAll]); + txt := StringReplace(txt, #10, CRLF, [rfReplaceAll]); + result := txt; +end;} + +{procedure StripNewLines(var txt: String; Replacement: String=' '); +begin + txt := StringReplace(txt, #13#10, Replacement, [rfReplaceAll]); + txt := StringReplace(txt, #13, Replacement, [rfReplaceAll]); + txt := StringReplace(txt, #10, Replacement, [rfReplaceAll]); +end;} + +{function GetLineBreak(LineBreakIndex: TLineBreaks): String; +begin + case LineBreakIndex of + lbsUnix: Result := LB_UNIX; + lbsMac: Result := LB_MAC; + else Result := CRLF; + end; +end;} + + +{*** + Mangle input text so that SynEdit can load it. +} +{procedure RemoveNullChars(var Text: String; var HasNulls: Boolean); +var + i, Len: Integer; +begin + HasNulls := False; + Len := Length(Text); + for i:=1 to Len do begin + if Text[i] = #0 then begin + Text[i] := #32; // space + HasNulls := True; + end; + end; +end;} + + +{*** + Get the path of a Windows(r)-shellfolder, specified by a KNOWNFOLDERID constant + @see https://docs.microsoft.com/en-us/windows/win32/shell/knownfolderid + @param TGUID constant + @return string Path +} +{function GetShellFolder(FolderId: TGUID): String; +var + Path: PWideChar; +begin + if Succeeded(SHGetKnownFolderPath(FolderId, 0, 0, Path)) then begin + Result := Path; + end else begin + Result := EmptyStr; + end; +end;} + + + +{*** + Remove special characters from a filename + + @param string Filename + @return string +} +{function ValidFilename(Str: String): String; +var + c: Char; +begin + Result := Str; + for c in TPath.GetInvalidFileNameChars do begin + Result := StringReplace(Result, c, '_', [rfReplaceAll]); + end; +end;} + + +{** + Unformat a formatted integer or float. Used for CSV export and composing WHERE clauses for grid editing. +} +{function UnformatNumber(Val: String): String; +var + i: Integer; + HasDecim: Boolean; + c: Char; +const + Numbers = ['0'..'9']; +begin + Result := ''; + HasDecim := False; + for i:=1 to Length(Val) do begin + c := Val[i]; + if (c = '-') and (i = 1) then + Result := Result + c + else if CharInSet(c, Numbers) then begin + if (c = '0') and (Result = '') then + // remove zeropadding + else + Result := Result + c + end else if (c = FormatSettings.DecimalSeparator) and (not HasDecim) then begin + if Result = '' then + Result := '0'; + Result := Result + '.'; + HasDecim := True; + end else if c <> FormatSettings.ThousandSeparator then + break; + end; + if Result = '' then + Result := '0'; +end;} + + +{*** + Return a formatted integer or float from a string + @param string Text containing a number + @return string +} +{function FormatNumber(str: String; Thousands: Boolean=True): String; Overload; +var + i, p, Left: Integer; +begin + Result := StringReplace(str, '.', FormatSettings.DecimalSeparator, [rfReplaceAll]); + if Thousands then begin + // Do not add thousand separators to zerofilled numbers + if ((Length(Result) >= 1) and (Result[1] = '0')) + or ((Length(Result) >= 2) and (Result[1] = '-') and (Result[2] = '0')) + then + Exit; + p := Pos(FormatSettings.DecimalSeparator, Result); + if p = 0 then p := Length(Result)+1; + Left := 2; + if (Length(Result) >= 1) and (Result[1] = '-') then + Left := 3; + if p > 0 then for i:=p-1 downto Left do begin + if (p-i) mod 3 = 0 then + Insert(FormatSettings.ThousandSeparator, Result, i); + end; + end; +end;} + + + +{*** + Return a formatted number from an integer + + @param int64 Number to format + @return string +} +{function FormatNumber(int: Int64; Thousands: Boolean=True): String; Overload; +begin + result := FormatNumber(IntToStr(int), Thousands); +end;} + + + +{*** + Return a formatted number from a float + This function is called by two overloaded functions + + @param double Number to format + @param integer Number of decimals + @return string +} +{function FormatNumber(flt: Double; decimals: Integer = 0; Thousands: Boolean=True): String; Overload; +begin + Result := Format('%10.'+IntToStr(decimals)+'f', [flt]); + Result := Trim(Result); + Result := FormatNumber(Result, Thousands); +end;} + + +{*** + Open URL or execute system command + + @param string Command or URL to execute + @param string Working directory, only usefull is first param is a system command +} +{procedure ShellExec(cmd: String; path: String=''; params: String=''; RunHidden: Boolean=False); +var + Msg: String; + ShowCmd: Integer; +begin + ShowCmd := IfThen(RunHidden, SW_HIDE, SW_SHOWNORMAL); + Msg := 'Executing shell command: "'+cmd+'"'; + if not path.IsEmpty then + Msg := Msg + ' path: "'+path+'"'; + if not params.IsEmpty then + Msg := Msg + ' params: "'+params+'"'; + MainForm.LogSQL(Msg, lcDebug); + ShellExecute(0, 'open', PChar(cmd), PChar(params), PChar(path), ShowCmd); +end;} + + + +{*** + Returns first word of a given text + @param string Given text + @return string First word-boundary +} +{function getFirstWord(text: String; MustStartWithWordChar: Boolean=True): String; +var + i : Integer; + wordChars, wordCharsFirst : TSysCharSet; +begin + result := ''; + text := trim( text ); + // First char in word must not be numerical. Fixes queries like + // /*!40000 SHOW ENGINES */ to be recognized as "result"-queries + // while not breaking getFirstWord in situations where the second + // or later char can be a number (fx the collation in createdatabase). + wordChars := ['a'..'z', 'A'..'Z', '0'..'9', '_', '-']; + if MustStartWithWordChar then + wordCharsFirst := wordChars - ['0'..'9'] + else + wordCharsFirst := wordChars; + i := 1; + + // Find beginning of the first word, ignoring non-alphanumeric chars at the very start + // @see bug #1692828 + while i < Length(text) do + begin + if CharInSet(text[i], wordCharsFirst) then + begin + // Found beginning of word! + break; + end; + if i = Length(text)-1 then + begin + // Give up in the very last loop, reset counter + // and break. We can't find the start of a word + i := 1; + break; + end; + inc(i); + end; + + // Add chars as long as they're alpha-numeric + while i <= Length(text) do + begin + if ((result = '') and CharInSet(text[i], wordCharsFirst)) or CharInSet(text[i], wordChars) then + begin + result := result + text[i]; + end + else + begin + // Stop here because we found a non-alphanumeric char. + // This applies to all different whitespaces, brackets, commas etc. + break; + end; + inc(i); + end; +end;} + + +{function RegExprGetMatch(Expression: String; var Input: String; ReturnMatchNum: Integer; DeleteFromSource, CaseInsensitive: Boolean): String; +var + rx: TRegExpr; +begin + Result := ''; + rx := TRegExpr.Create; + rx.ModifierI := CaseInsensitive; + rx.Expression := Expression; + if rx.Exec(Input) then begin + if rx.SubExprMatchCount >= ReturnMatchNum then begin + Result := rx.Match[ReturnMatchNum]; + if DeleteFromSource then begin + Delete(Input, rx.MatchPos[ReturnMatchNum], rx.MatchLen[ReturnMatchNum]); + Input := Trim(Input); + end; + end; + end; + rx.Free; +end;} + + +{function RegExprGetMatch(Expression: String; Input: String; ReturnMatchNum: Integer): String; +begin + // Version without possibility to delete captured match from input + Result := RegExprGetMatch(Expression, Input, ReturnMatchNum, False, False); +end;} + + +{function ExecRegExprI(const ARegExpr, AInputStr: RegExprString): Boolean; +var + r: TRegExpr; +begin + Result := False; + r := TRegExpr.Create; + r.ModifierI := True; + try + r.Expression := ARegExpr; + Result := r.Exec(AInputStr); + finally + r.Free; + end; +end;} + + +{** + Format a filesize to automatically use the best fitting expression + 16 100 000 Bytes -> 16,1 MB + 4 500 Bytes -> 4,5 KB + @param Int64 Number of Bytes + @param Byte Decimals to display when bytes is bigger than 1M +} +{function FormatByteNumber( Bytes: Int64; Decimals: Byte = 1 ): String; Overload; +begin + if Bytes >= FSIZE_PB then + Result := FormatNumber( Bytes / SIZE_PB, Decimals ) + NAME_PB + else if Bytes >= FSIZE_TB then + Result := FormatNumber( Bytes / SIZE_TB, Decimals ) + NAME_TB + else if Bytes >= FSIZE_GB then + Result := FormatNumber( Bytes / SIZE_GB, Decimals ) + NAME_GB + else if Bytes >= FSIZE_MB then + Result := FormatNumber( Bytes / SIZE_MB, Decimals ) + NAME_MB + else if Bytes >= FSIZE_KB then + Result := FormatNumber( Bytes / SIZE_KB, Decimals ) + NAME_KB + else + Result := FormatNumber( Bytes ) + NAME_BYTES +end;} + + +{** + An overloaded function of the previous one which can + take a string as input +} +{function FormatByteNumber( Bytes: String; Decimals: Byte = 1 ): String; Overload; +begin + Result := FormatByteNumber( MakeInt(Bytes), Decimals ); +end;} + + +{** + Format a number of seconds to a human readable time format + @param Cardinal Number of seconds + @result String 12:34:56.7 +} +{function FormatTimeNumber(Seconds: Double; DisplaySeconds: Boolean; MilliSecondsPrecision: Integer=1): String; +var + d, h, m, s, ms: Integer; + msStr: String; +begin + s := TruncDef(Seconds, 0); + ms := TruncDef((Seconds - s) * Power(10, MilliSecondsPrecision), 0); // Milliseconds, with variable precision/digits + msStr := IntToStr(ms).PadLeft(MilliSecondsPrecision, '0'); + d := s div (60*60*24); + s := s mod (60*60*24); + h := s div (60*60); + s := s mod (60*60); + m := s div 60; + s := s mod 60; + if d > 0 then begin + if DisplaySeconds then begin + Result := Format('%d '+_('days')+', %.2d:%.2d:%.2d', [d, h, m, s]); + Result := Result + '.' + msStr; // Append milliseconds + end + else begin + Result := Format('%d '+_('days')+', %.2d:%.2d h', [d, h, m]); + end; + end else begin + if DisplaySeconds then begin + Result := Format('%.2d:%.2d:%.2d', [h, m, s]); + Result := Result + '.' + msStr; // Append milliseconds + end + else begin + Result := Format('%.2d:%.2d h', [h, m]); + end; + end; +end;} + + +{function GetTempDir: String; +var + TempPath: array[0..MAX_PATH] of Char; +begin + GetTempPath(MAX_PATH, PChar(@TempPath)); + Result := StrPas(TempPath); +end;} + + +{** + Save a textfile with unicode +} +{procedure SaveUnicodeFile(Filename: String; Text: String; Encoding: TEncoding); +var + Writer: TStreamWriter; +begin + Writer := TStreamWriter.Create(Filename, False, Encoding); + Writer.Write(Text); + Writer.Free; +end;} + + +{procedure OpenTextFile(const Filename: String; out Stream: TFileStream; var Encoding: TEncoding); +var + Header: TBytes; + BomLen: Integer; +begin + // Open a textfile and return a stream. Detect its encoding if not passed by the caller + Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone); + if Encoding = nil then + Encoding := DetectEncoding(Stream); + // If the file contains a BOM, advance the stream's position + BomLen := 0; + if Length(Encoding.GetPreamble) > 0 then begin + SetLength(Header, Length(Encoding.GetPreamble)); + Stream.ReadBuffer(Pointer(Header)^, Length(Header)); + if CompareMem(Header, Encoding.GetPreamble, SizeOf(Header)) then + BomLen := Length(Encoding.GetPreamble); + end; + Stream.Position := BomLen; +end;} + + +{** + Detect stream's content encoding through SynEdit's GetEncoding. Result can be: + UTF-16 BE with BOM + UTF-16 LE with BOM + UTF-8 with or without BOM + ANSI + Aimed to work better than WideStrUtils.IsUTF8String() which didn't work in any test case here. + @see http://en.wikipedia.org/wiki/Byte_Order_Mark + Could also do that with TEncoding.GetBufferEncoding, but that relies on the file having a BOM +} +{function DetectEncoding(Stream: TStream): TEncoding; +var + SynEnc: TSynEncoding; + WithBOM: Boolean; +begin + SynEnc := SynUnicode.GetEncoding(Stream, WithBOM); + case SynEnc of + seUTF8: begin + if WithBOM then + Result := TEncoding.UTF8 + else + Result := UTF8NoBOMEncoding; + end; + seUTF16LE: Result := TEncoding.Unicode; + seUTF16BE: Result := TEncoding.BigEndianUnicode; + seAnsi: Result := TEncoding.ANSI; + else Result := UTF8NoBOMEncoding; + end; +end;} + + +{function ReadTextfileChunk(Stream: TFileStream; Encoding: TEncoding; ChunkSize: Int64 = 0): String; +const + BufferPadding = 1; +var + DataLeft, StartPosition: Int64; + LBuffer: TBytes; + i: Integer; +begin + // Read a chunk or the complete contents out of a textfile, opened by OpenTextFile() + if Stream.Size = 0 then begin + Result := ''; + Exit; + end; + + StartPosition := Stream.Position; + DataLeft := Stream.Size - Stream.Position; + if (ChunkSize = 0) or (ChunkSize > DataLeft) then + ChunkSize := DataLeft; + + i := 0; + while True do begin + Inc(i); + try + SetLength(LBuffer, ChunkSize); + Stream.ReadBuffer(Pointer(LBuffer)^, ChunkSize); + LBuffer := Encoding.Convert(Encoding, TEncoding.Unicode, LBuffer); + // Success, exit loop + Break; + except + on E:EEncodingError do begin + if i=10 then // Give up + Raise; + Stream.Position := StartPosition; + Inc(ChunkSize, BufferPadding); + end; + end; + end; + + Result := TEncoding.Unicode.GetString(LBuffer); +end;} + + +{function ReadTextfile(Filename: String; Encoding: TEncoding): String; +var + Stream: TFileStream; +begin + // Read a text file into memory + OpenTextfile(Filename, Stream, Encoding); + Result := ReadTextfileChunk(Stream, Encoding); + Stream.Free; +end;} + + +{function ReadBinaryFile(Filename: String; MaxBytes: Int64): AnsiString; +var + Stream: TFileStream; +begin + Stream := TFileStream.Create(Filename, fmOpenRead or fmShareDenyNone); + Stream.Position := 0; + if (MaxBytes < 1) or (MaxBytes > Stream.Size) then MaxBytes := Stream.Size; + SetLength(Result, MaxBytes); + Stream.Read(PAnsiChar(Result)^, Length(Result)); + Stream.Free; +end;} + + +{procedure StreamToClipboard(Text, HTML: TStream); +var + TextContent, HTMLContent, HTMLHeader, NullPos: AnsiString; + GlobalMem: HGLOBAL; + lp: PChar; + ClpLen: Integer; + CF_HTML: Word; + StartHTML, EndHTML, StartFragment, EndFragment: Integer; +const + PosFormat: AnsiString = '%.10d'; + + procedure ReplacePos(Name: AnsiString; Value: Integer); + var NewPos: AnsiString; + begin + NewPos := Format(PosFormat, [Value]); + HTMLContent := StringReplace(HTMLContent, Name+':'+NullPos, Name+':'+NewPos, []); + end; +begin + // Copy unicode text to clipboard + if Assigned(Text) then begin + SetLength(TextContent, Text.Size); + Text.Position := 0; + Text.Read(PAnsiChar(TextContent)^, Text.Size); + Clipboard.TryAsText := Utf8ToString(TextContent); + SetString(TextContent, nil, 0); + end; + + if Assigned(HTML) then begin + // If wanted, add a HTML portion, so formatted text can be pasted in WYSIWYG + // editors (mostly MS applications). + // Note that the content is UTF8 encoded ANSI. Using unicode variables results in raw + // text pasted in editors. TODO: Find out why and optimize redundant code away by a loop. + OpenClipBoard(0); + CF_HTML := RegisterClipboardFormat('HTML Format'); + SetLength(HTMLContent, HTML.Size); + HTML.Position := 0; + HTML.Read(PAnsiChar(HTMLContent)^, HTML.Size); + if Pos(AnsiString('Version:'), HTMLContent) = 0 then begin + // Only required if header was not already prepended by SynEdit, e.g. in grid export of SQL Inserts + NullPos := Format(PosFormat, [0]); + HTMLHeader := 'Version:0.9' + sLineBreak + + 'StartHTML:' + NullPos + sLineBreak + + 'EndHTML:' + NullPos + sLineBreak + + 'StartFragment:' + NullPos + sLineBreak + + 'EndFragment:' + NullPos + sLineBreak; + StartHTML := Length(HTMLHeader); + HTMLContent := HTMLHeader + HTMLContent; + EndHTML := Length(HTMLContent); + StartFragment := Pos(AnsiString('<body>'), HTMLContent) + 6; + EndFragment := Pos(AnsiString('</body'), HTMLContent)-1; + ReplacePos('StartHTML', StartHTML); + ReplacePos('EndHTML', EndHTML); + ReplacePos('StartFragment', StartFragment); + ReplacePos('EndFragment', EndFragment); + end; + ClpLen := Length(HTMLContent) + 1; + GlobalMem := GlobalAlloc(GMEM_DDESHARE + GMEM_MOVEABLE, ClpLen); + lp := GlobalLock(GlobalMem); + Move(PAnsiChar(HTMLContent)^, lp[0], ClpLen); + SetString(HTMLContent, nil, 0); + GlobalUnlock(GlobalMem); + SetClipboardData(CF_HTML, GlobalMem); + CloseClipboard; + end; +end;} + + +{procedure FixVT(VT: TVirtualStringTree; MultiLineCount: Word=1); +var + SingleLineHeight: Integer; + Node: PVirtualNode; +begin + // This is called either in some early stage, or from preferences dialog + VT.BeginUpdate; + SingleLineHeight := GetTextHeight(VT.Font) + 7; + // Multiline nodes? + VT.DefaultNodeHeight := SingleLineHeight * MultiLineCount; + VT.Header.Height := SingleLineHeight; + // Apply new height to multi line grid nodes + Node := VT.GetFirstInitialized; + while Assigned(Node) do begin + VT.NodeHeight[Node] := VT.DefaultNodeHeight; + // Nodes have vsMultiLine through InitNode event + VT.MultiLine[Node] := MultiLineCount > 1; + Node := VT.GetNextInitialized(Node); + end; + VT.EndUpdate; + // Disable hottracking in non-Vista mode, looks ugly in XP, but nice in Vista + if (toUseExplorerTheme in VT.TreeOptions.PaintOptions) and (Win32MajorVersion >= 6) then + VT.TreeOptions.PaintOptions := VT.TreeOptions.PaintOptions + [toHotTrack] + else + VT.TreeOptions.PaintOptions := VT.TreeOptions.PaintOptions - [toHotTrack]; + VT.OnGetHint := MainForm.AnyGridGetHint; + VT.OnScroll := MainForm.AnyGridScroll; + VT.OnMouseWheel := MainForm.AnyGridMouseWheel; + VT.ShowHint := True; + + if toGridExtensions in VT.TreeOptions.MiscOptions then + VT.HintMode := hmHint // Show cell contents with linebreakds in datagrid and querygrid's + else + VT.HintMode := hmTooltip; // Just a quick tooltip for clipped nodes + // Apply case insensitive incremental search event + if VT.IncrementalSearch <> VirtualTrees.Types.isNone then + VT.OnIncrementalSearch := Mainform.AnyGridIncrementalSearch; + VT.OnStartOperation := Mainform.AnyGridStartOperation; + VT.OnEndOperation := Mainform.AnyGridEndOperation; +end;} + + +{function GetTextHeight(Font: TFont): Integer; +var + DC: HDC; + SaveFont: HFont; + SysMetrics, Metrics: TTextMetric; +begin + // Code taken from StdCtrls.TCustomEdit.AdjustHeight + DC := GetDC(0); + GetTextMetrics(DC, SysMetrics); + SaveFont := SelectObject(DC, Font.Handle); + GetTextMetrics(DC, Metrics); + SelectObject(DC, SaveFont); + ReleaseDC(0, DC); + Result := Metrics.tmHeight; +end;} + + +{function ColorAdjustBrightness(Col: TColor; Shift: SmallInt): TColor; +var + Lightness: Byte; +begin + // If base color is bright, make bg color darker (grey), and vice versa, so that + // colors work with high contrast mode for accessibility + Lightness := GetLightness(Col); + if (Lightness < 128) and (Shift < 0) then + Shift := Abs(Shift) + else if (Lightness > 128) and (Shift > 0) then + Shift := 0 - Abs(Shift); + Result := ColorAdjustLuma(Col, Shift, true); +end;} + + +{procedure DeInitializeVTNodes(Sender: TBaseVirtualTree); +var + Node: PVirtualNode; +begin + // Forces a VirtualTree to (re-)initialize its nodes. + // I wonder why this is not implemented in VirtualTree. + Node := Sender.GetFirstInitialized; + while Assigned(Node) do begin + Node.States := Node.States - [vsInitialized]; + Node := Sender.GetNextInitialized(Node); + end; +end;} + + +{function FindNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode): PVirtualNode; +var + Node: PVirtualNode; +begin + // Helper to find a node by its index + Result := nil; + Node := nil; + try + if Assigned(ParentNode) then + Node := VT.GetFirstChild(ParentNode) + else + Node := VT.GetFirst; + except + // Sporadically, TBaseVirtualTree.GetFirst throws an exception when reading FRoot.FirstChild + // Tab restoring is sometimes crashing for that reason. + end; + while Assigned(Node) do begin + // Note: Grid.RootNodeCount is unfortunately Cardinal, not UInt64. + if Node.Index = idx then begin + Result := Node; + break; + end; + Node := VT.GetNextSibling(Node); + end; +end;} + + +{function SelectNode(VT: TVirtualStringTree; idx: Int64; ParentNode: PVirtualNode=nil): Boolean; overload; +var + Node: PVirtualNode; +begin + // Helper to focus and highlight a node by its index + Node := FindNode(VT, idx, ParentNode); + if Assigned(Node) then + Result := SelectNode(VT, Node) + else + Result := False; +end;} + + +{function SelectNode(VT: TVirtualStringTree; Node: PVirtualNode; ClearSelection: Boolean=True): Boolean; overload; +var + OldFocus: PVirtualNode; + MinimumColumnIndex: TColumnIndex; +begin + if Node = VT.RootNode then + Node := nil; + OldFocus := VT.FocusedNode; + Result := True; + if (Node <> OldFocus) and Assigned(VT.OnFocusChanging) then begin + VT.OnFocusChanging(VT, OldFocus, Node, VT.FocusedColumn, VT.FocusedColumn, Result); + end; + if Result then begin + if ClearSelection then + VT.ClearSelection; + VT.FocusedNode := Node; + MinimumColumnIndex := VT.Header.Columns.GetFirstVisibleColumn(True); + if VT.FocusedColumn < MinimumColumnIndex then + VT.FocusedColumn := MinimumColumnIndex; + VT.Selected[Node] := True; + VT.ScrollIntoView(Node, False); + if (OldFocus = Node) and Assigned(VT.OnFocusChanged) then + VT.OnFocusChanged(VT, Node, VT.FocusedColumn); + end; +end;} + + +{procedure GetVTSelection(VT: TVirtualStringTree; var SelectedCaptions: TStringList; var FocusedCaption: String); +var + Node: PVirtualNode; + InvalidationTag: Integer; +begin + // Return captions of selected nodes + InvalidationTag := vt.Tag; + vt.Tag := VTREE_LOADED; + SelectedCaptions.Clear; + Node := GetNextNode(VT, nil, true); + while Assigned(Node) do begin + SelectedCaptions.Add(VT.Text[Node, VT.Header.MainColumn]); + if Node = VT.FocusedNode then begin + FocusedCaption := VT.Text[Node, VT.Header.MainColumn]; + end; + Node := GetNextNode(VT, Node, true); + end; + vt.Tag := InvalidationTag; +end;} + + +{procedure SetVTSelection(VT: TVirtualStringTree; SelectedCaptions: TStringList; FocusedCaption: String); +var + Node: PVirtualNode; + idx: Integer; + DoFocusChange: Boolean; +begin + // Restore selected nodes based on captions list + DoFocusChange := False; + Node := GetNextNode(VT, nil, false); + while Assigned(Node) do begin + idx := SelectedCaptions.IndexOf(VT.Text[Node, VT.Header.MainColumn]); + if idx > -1 then + VT.Selected[Node] := True; + if (not FocusedCaption.IsEmpty) and (VT.Text[Node, VT.Header.MainColumn] = FocusedCaption) then begin + VT.FocusedNode := Node; + DoFocusChange := True; + end; + Node := GetNextNode(VT, Node, false); + end; + // Fire focus change event if there was a focused one before + if DoFocusChange and Assigned(VT.OnFocusChanged) then begin + VT.OnFocusChanged(VT, VT.FocusedNode, VT.FocusedColumn); + end; +end;} + + +{function GetNextNode(Tree: TVirtualStringTree; CurrentNode: PVirtualNode; Selected: Boolean=False): PVirtualNode; +begin + // Get next visible + selected node. Not possible with VTree's own functions. + Result := CurrentNode; + while True do begin + if Selected then begin + if not Assigned(Result) then + Result := Tree.GetFirstSelected + else + Result := Tree.GetNextSelected(Result); + end else begin + if not Assigned(Result) then + Result := Tree.GetFirst + else + Result := Tree.GetNext(Result); + end; + if (not Assigned(Result)) or Tree.IsVisible[Result] then + break; + end; +end;} + + +{function GetPreviousNode(Tree: TVirtualStringTree; CurrentNode: PVirtualNode; Selected: Boolean=False): PVirtualNode; +begin + // Get previous visible + selected node. + Result := CurrentNode; + while True do begin + if Selected then begin + if not Assigned(Result) then begin + Result := Tree.GetLast; + if not Tree.Selected[Result] then + Result := Tree.GetPreviousSelected(Result); + end else + Result := Tree.GetPreviousSelected(Result); + end else begin + if not Assigned(Result) then + Result := Tree.GetLast + else + Result := Tree.GetPrevious(Result); + end; + if (not Assigned(Result)) or Tree.IsVisible[Result] then + break; + end; +end;} + + +{function DateBackFriendlyCaption(d: TDateTime): String; +var + MonthsAgo, DaysAgo, HoursAgo, MinutesAgo: Int64; +begin + MonthsAgo := MonthsBetween(Now, d); + DaysAgo := DaysBetween(Now, d); + HoursAgo := HoursBetween(Now, d); + MinutesAgo := MinutesBetween(Now, d); + if MonthsAgo = 1 then Result := f_('%s month ago', [FormatNumber(MonthsAgo)]) + else if MonthsAgo > 1 then Result := f_('%s months ago', [FormatNumber(MonthsAgo)]) + else if DaysAgo = 1 then Result := f_('%s day ago', [FormatNumber(DaysAgo)]) + else if DaysAgo > 1 then Result := f_('%s days ago', [FormatNumber(DaysAgo)]) + else if HoursAgo = 1 then Result := f_('%s hour ago', [FormatNumber(HoursAgo)]) + else if HoursAgo > 1 then Result := f_('%s hours ago', [FormatNumber(HoursAgo)]) + else if MinutesAgo = 1 then Result := f_('%s minute ago', [FormatNumber(MinutesAgo)]) + else if MinutesAgo > 0 then Result := f_('%s minutes ago', [FormatNumber(MinutesAgo)]) + else Result := _('less than a minute ago'); +end;} + + +{function DateTimeToStrDef(DateTime: TDateTime; Default: String) : String; +begin + try + if DateTime = 0 then + Result := Default + else + Result := DateTimeToStr(DateTime); + except + on EInvalidOp do Result := Default; + end; +end;} + + +{function TruncDef(X: Real; Default: Int64): Int64; +begin + try + Result := Trunc(X); + except + on EInvalidOp do Result := Default; + end; +end;} + + +{procedure ExplodeQuotedList(Text: String; var List: TStringList); +var + i: Integer; + Quote: Char; + Opened, Closed: Boolean; + Item: String; +begin + Text := Trim(Text); + if Length(Text) > 0 then + Quote := Text[1] + else + Quote := '`'; + Opened := False; + Closed := True; + Item := ''; + for i:=1 to Length(Text) do begin + if Text[i] = Quote then begin + Opened := not Opened; + Closed := not Closed; + if Closed then begin + List.Add(Item); + Item := ''; + end; + Continue; + end; + if Opened and (not Closed) then + Item := Item + Text[i]; + end; +end;} + + +{function GetLightness(AColor: TColor): Byte; +var + R, G, B: Byte; + MaxValue, MinValue: Double; + Lightness: Double; +begin + R := GetRValue(ColorToRGB(AColor)); + G := GetGValue(ColorToRGB(AColor)); + B := GetBValue(ColorToRGB(AColor)); + MaxValue := Max(Max(R,G),B); + MinValue := Min(Min(R,G),B); + Lightness := (((MaxValue + MinValue) * 240) + 255 ) / 510; + Result := Round(Lightness); +end;} + + + +{ *** TSortItem } + +{procedure TSortItem.Assign(Source: TPersistent); +var + SourceItem: TSortItem; +begin + if Source is TSortItem then begin + SourceItem := Source as TSortItem; + Column := SourceItem.Column; + Order := SourceItem.Order; + end + else + Inherited; +end;} + + +{ *** TSortItems } + +{function TSortItems.AddNew(Column: String=''; Order: TSortItemOrder=sioAscending): TSortItem; +begin + Result := TSortItem.Create; + Result.Column := Column; + Result.Order := Order; + Add(Result); +end;} + + +{function TSortItems.ComposeOrderClause(Connection: TDBConnection): String; +var + SortItem: TSortItem; + SortOrder: String; +begin + // Concat all sort options to an ORDER BY clause + Result := ''; + for SortItem in Self do begin + if Result <> '' then + Result := Result + ', '; + if SortItem.Order = sioAscending then + SortOrder := Connection.GetSQLSpecifity(spOrderAsc) + else + SortOrder := Connection.GetSQLSpecifity(spOrderDesc); + Result := Result + Connection.QuoteIdent(SortItem.Column) + ' ' + SortOrder; + end; +end;} + + +{function TSortItems.FindByColumn(Column: String): TSortItem; +var + SortItem: TSortItem; +begin + Result := nil; + for SortItem in Self do begin + if SortItem.Column = Column then begin + Result := SortItem; + Break; + end; + end; +end;} + + +{procedure TSortItems.Assign(Source: TSortItems); +var + Item, ItemCopy: TSortItem; +begin + Clear; + for Item in Source do begin + ItemCopy := AddNew; + ItemCopy.Assign(Item); + end; +end;} + + +{ *** TDBObjectEditor } + +{constructor TDBObjectEditor.Create(AOwner: TComponent); +begin + inherited; + // Do not set alClient via DFM! In conjunction with ExplicitXXX properties that + // repeatedly breaks the GUI layout when you reload the project + Align := alClient; + FMainSynMemo := nil; + DBObject := nil; + TranslateComponent(Self); +end;} + +{destructor TDBObjectEditor.Destroy; +begin + inherited; +end;} + +{procedure TDBObjectEditor.SetModified(Value: Boolean); +begin + FModified := Value; +end;} + +{function TDBObjectEditor.ObjectExists: Boolean; +begin + Result := not DBObject.Name.IsEmpty; +end;} + +{procedure TDBObjectEditor.Init(Obj: TDBObject); +var + editName: TWinControl; + SynMemo: TSynMemo; + popup: TPopupMenu; + Item: TMenuItem; + i: Integer; + IsRefresh: Boolean; +begin + Mainform.ShowStatusMsg(_('Initializing editor ...')); + Mainform.LogSQL(Self.ClassName+'.Init, using object "'+Obj.Name+'"', lcDebug); + TExtForm.FixControls(Self); + IsRefresh := Assigned(DBObject) and DBObject.IsSameAs(Obj); + if IsRefresh and Assigned(FMainSynMemo) then + FMainSynMemoPreviousTopLine := FMainSynMemo.TopLine + else + FMainSynMemoPreviousTopLine := 0; + DBObject := TDBObject.Create(Obj.Connection); + DBObject.Assign(Obj); + Mainform.UpdateEditorTab; + Screen.Cursor := crHourglass; + // Enable user to start typing immediately when creating a new object + if DBObject.Name = '' then begin + editName := FindComponent('editName') as TWinControl; + if Assigned(editName) and editName.CanFocus then + editName.SetFocus; + end; + + for i:=0 to ComponentCount-1 do begin + if not(Components[i] is TSynMemo) then + Continue; + + SynMemo := Components[i] as TSynMemo; + if (not Assigned(SynMemo)) or Assigned(SynMemo.PopupMenu) then + Continue; + + popup := TPopupMenu.Create(Self); + popup.Images := MainForm.VirtualImageListMain; + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actCopy; + popup.Items.Add(Item); + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actCut; + popup.Items.Add(Item); + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actPaste; + popup.Items.Add(Item); + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actSelectAll; + popup.Items.Add(Item); + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actSaveSynMemoToTextfile; + popup.Items.Add(Item); + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actToggleComment; + popup.Items.Add(Item); + + Item := TMenuItem.Create(popup); + Item.Action := MainForm.actReformatSQL; + popup.Items.Add(Item); + + SynMemo.PopupMenu := popup; + + end; + +end;} + +{function TDBObjectEditor.DeInit: TModalResult; +var + Msg, ObjType: String; +begin + // Ask for saving modifications + Result := mrOk; + if Modified then begin + ObjType := _(LowerCase(DBObject.ObjType)); + // Todo: no save button for objects without minimum requirements, such as name. See #1134 + if DBObject.Name <> '' then + Msg := f_('Save modified %s "%s"?', [ObjType, DBObject.Name]) + else + Msg := f_('Save new %s?', [ObjType]); + Result := MessageDialog(Msg, mtConfirmation, [mbYes, mbNo, mbCancel]); + case Result of + mrYes: Result := ApplyModifications; + mrNo: Modified := False; + end; + end; +end;} + + + + +// Following code taken from OneInst.pas, http://assarbad.net/de/stuff/!import/nico.old/ +// Slightly modified to better integrate that into our code, comments translated from german. + +// Fetch and separate command line parameters into strings +{function ParamBlobToStr(lpData: Pointer): String; +var + pStr: PChar; +begin + pStr := lpData; + Result := string(pStr); +end;} + +// Pack current command line parameters +{function ParamStrToBlob(out cbData: DWORD): Pointer; +var + cmd: String; +begin + cmd := GetCommandLine; + cbData := Length(cmd)*2 + 3; + Result := PChar(cmd); +end;} + +{procedure HandleSecondInstance; +var + Run: DWORD; + Now: DWORD; + Msg: TMsg; + Wnd: HWND; + Dat: TCopyDataStruct; +begin + // MessageBox(0, 'already running', nil, MB_ICONINFORMATION); + // Send a message to all main windows (HWND_BROADCAST) with the identical, + // previously registered message id. We should only get reply from 0 or 1 + // instances. + // (Broadcast should only be called with registered message ids!) + + SendMessage(HWND_BROADCAST, SecondInstMsgId, GetCurrentThreadId, 0); + + // Waiting for reply by first instance. For those of you which didn't knew: + // Threads have message queues too ;o) + Wnd := 0; + Run := GetTickCount; + while True do + begin + if PeekMessage(Msg, 0, SecondInstMsgId, SecondInstMsgId, PM_NOREMOVE) then + begin + GetMessage(Msg, 0, SecondInstMsgId, SecondInstMsgId); + if Msg.message = SecondInstMsgId then + begin + Wnd := Msg.wParam; + Break; + end; + end; + Now := GetTickCount; + if Now < Run then + Run := Now; // Avoid overflow, each 48 days. + if Now - Run > 5000 then + Break; + end; + + if (Wnd <> 0) and IsWindow(Wnd) then + begin + // As a reply we got a handle to which we now send current parameters + Dat.dwData := SecondInstMsgId; + Dat.lpData := ParamStrToBlob(Dat.cbData); + SendMessage(Wnd, WM_COPYDATA, 0, LPARAM(@Dat)); + // Leads to an AV in 64bit mode. See issue #3475: + // FreeMemory(Dat.lpData); + + // Bring first instance to front + if IsIconic(Wnd) then + ShowWindow(Wnd, SW_RESTORE); + BringWindowToTop(Wnd); + SetForegroundWindow(Wnd); + end; +end;} + +{function CheckForSecondInstance: Boolean; +var + Loop: Integer; + MutexName: PChar; +begin + // Try to create a system wide named kernel object (mutex). And check if that + // already exists. + // The name of such a mutex must not be longer than MAX_PATH (260) chars and + // can contain all chars but not '\' + + Result := False; + MutexName := PChar(APPNAME); + for Loop := lstrlen(MutexName) to MAX_PATH - 1 do + begin + MutexHandle := CreateMutex(nil, False, MutexName); + if (MutexHandle = 0) and (GetLastError = INVALID_HANDLE_VALUE) then + // Looks like there is already a mutex using this name + // Try to solve that by appending an underscore + lstrcat(MutexName, '_') + else + // At least no naming conflict + Break; + end; + + case GetLastError of + 0: begin + // We created the mutex, so this is the first instance + end; + ERROR_ALREADY_EXISTS: + begin + // There is already one instance + try + HandleSecondInstance; + finally + // Terminating is done in .dpr file, before Application.Initialize + Result := True; + end; + end; + else + // No clue why we should get here. Oh, maybe Microsoft has changed rules, again. + // However, we return false and let the application start + end; +end;} + + +{function GetParentFormOrFrame(Comp: TWinControl): TWinControl; +begin + Result := Comp; + while True do begin + try + Result := Result.Parent; + except + on E:EAccessViolation do + Break; + end; + // On a windows shutdown, GetParentForm() seems sporadically unable to find the owner form + // In that case we would cause an exception when accessing it. Emergency break in that case. + // See issue #1462 + if (not Assigned(Result)) or (Result is TCustomForm) or (Result is TFrame) then + break; + end; +end;} + + +{function KeyPressed(Code: Integer): Boolean; +var + State: TKeyboardState; +begin + // Checks whether a key is pressed, defined by virtual key code + GetKeyboardState(State); + Result := (State[Code] and 128) <> 0; +end;} + + +{function GeneratePassword(Len: Integer): String; +var + i: Integer; + CharTable: String; +const + Consos = 'bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'; + Vocals = 'aeiouAEIOU'; + Numbers = '123456789'; +begin + // Create a random, mnemonic password + SetLength(Result, Len); + for i:=1 to Len do begin + if Random(4) = 1 then + CharTable := Numbers + else if i mod 2 = 0 then + CharTable := Vocals + else + CharTable := Consos; + Result[i] := CharTable[Random(Length(CharTable)-1)+1]; + end; +end;} + + +{procedure InvalidateVT(VT: TVirtualStringTree; RefreshTag: Integer; ImmediateRepaint: Boolean); +begin + // Avoid AVs in OnDestroy events + if not Assigned(VT) then + Exit; + VT.Tag := RefreshTag; + if ImmediateRepaint then + VT.Repaint + else + VT.Invalidate; +end;} + + +{function CharAtPos(Str: String; Pos: Integer): Char; +begin + // Access char in string without causing access violation + if Length(Str) < Pos then + Result := #0 + else + Result := Str[Pos]; +end;} + + +{function CompareAnyNode(Text1, Text2: String): Integer; +var + Number1, Number2 : Extended; + a1, a2, b1, b2: Char; + NumberMode: Boolean; +const + Numbers = ['0'..'9']; +begin + Result := 0; + // Apply different comparisons for numbers and text + a1 := CharAtPos(Text1, 1); + a2 := CharAtPos(Text1, 2); + b1 := CharAtPos(Text2, 1); + b2 := CharAtPos(Text2, 2); + NumberMode := ((a1='-') and (CharInSet(a2, Numbers)) or CharInSet(a1, Numbers)) + and ((b1='-') and (CharInSet(b2, Numbers)) or CharInSet(b1, Numbers)); + if NumberMode then begin + // Assuming numeric values + Number1 := MakeFloat(Text1); + Number2 := MakeFloat(Text2); + if Number1 > Number2 then + Result := 1 + else if Number1 = Number2 then + Result := 0 + else if Number1 < Number2 then + Result := -1; + end; + if (not NumberMode) or (Result=0) then begin + // Compare Strings + Result := CompareText(Text1, Text2); + end; +end;} + + +{function StringListCompareAnythingAsc(List: TStringList; Index1, Index2: Integer): Integer; +begin + // Sort TStringList items, containing numbers or strings, ascending + Result := CompareAnyNode(List[Index1], List[Index2]); +end;} + + +{function StringListCompareAnythingDesc(List: TStringList; Index1, Index2: Integer): Integer; +begin + // Sort TStringList items, containing numbers or strings, descending + Result := CompareAnyNode(List[Index2], List[Index1]); +end;} + + +{function StringListCompareByValue(List: TStringList; Index1, Index2: Integer): Integer; +begin + // Sort TStringList items which are stored as name=value pairs + Result := CompareAnyNode(List.ValueFromIndex[Index2], List.ValueFromIndex[Index1]); +end;} + + +{function StringListCompareByLength(List: TStringList; Index1, Index2: Integer): Integer; +begin + // Sort TStringList items by their length + Result := CompareValue(List[Index2].Length, List[Index1].Length); +end;} + + +{** + Return compile date/time from passed .exe name + Code taken and modified from Michael Puff + http://www.michael-puff.de/Programmierung/Delphi/Code-Snippets/GetImageLinkTimeStamp.shtml +} +{function GetImageLinkTimeStamp(const FileName: string): TDateTime; +const + INVALID_SET_FILE_POINTER = DWORD(-1); + BorlandMagicTimeStamp = $2A425E19; // Delphi 4-6 (and above?) + FileTime1970: TFileTime = (dwLowDateTime:$D53E8000; dwHighDateTime:$019DB1DE); +type + PImageSectionHeaders = ^TImageSectionHeaders; + TImageSectionHeaders = array [Word] of TImageSectionHeader; +type + PImageResourceDirectory = ^TImageResourceDirectory; + TImageResourceDirectory = packed record + Characteristics: DWORD; + TimeDateStamp: DWORD; + MajorVersion: Word; + MinorVersion: Word; + NumberOfNamedEntries: Word; + NumberOfIdEntries: Word; + end; +var + FileHandle: THandle; + BytesRead: DWORD; + ImageDosHeader: TImageDosHeader; + ImageNtHeaders: TImageNtHeaders; + SectionHeaders: PImageSectionHeaders; + Section: Word; + ResDirRVA: DWORD; + ResDirSize: DWORD; + ResDirRaw: DWORD; + ResDirTable: TImageResourceDirectory; + FileTime: TFileTime; + TimeStamp: DWord; +begin + TimeStamp := 0; + Result := 0; + // Open file for read access + FileHandle := CreateFile(PChar(FileName), GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, 0, 0); + if (FileHandle <> INVALID_HANDLE_VALUE) then try + // Read MS-DOS header to get the offset of the PE32 header + // (not required on WinNT based systems - but mostly available) + if not ReadFile(FileHandle, ImageDosHeader, SizeOf(TImageDosHeader), + BytesRead, nil) or (BytesRead <> SizeOf(TImageDosHeader)) or + (ImageDosHeader.e_magic <> IMAGE_DOS_SIGNATURE) then begin + ImageDosHeader._lfanew := 0; + end; + // Read PE32 header (including optional header + if (SetFilePointer(FileHandle, ImageDosHeader._lfanew, nil, FILE_BEGIN) = INVALID_SET_FILE_POINTER) then + Exit; + if not(ReadFile(FileHandle, ImageNtHeaders, SizeOf(TImageNtHeaders), BytesRead, nil) and (BytesRead = SizeOf(TImageNtHeaders))) then + Exit; + // Validate PE32 image header + if (ImageNtHeaders.Signature <> IMAGE_NT_SIGNATURE) then + Exit; + // Seconds since 1970 (UTC) + TimeStamp := ImageNtHeaders.FileHeader.TimeDateStamp; + + // Check for Borland's magic value for the link time stamp + // (we take the time stamp from the resource directory table) + if (ImageNtHeaders.FileHeader.TimeDateStamp = BorlandMagicTimeStamp) then + with ImageNtHeaders, FileHeader, OptionalHeader do begin + // Validate Optional header + if (SizeOfOptionalHeader < IMAGE_SIZEOF_NT_OPTIONAL_HEADER) or (Magic <> IMAGE_NT_OPTIONAL_HDR_MAGIC) then + Exit; + // Read section headers + SectionHeaders := + GetMemory(NumberOfSections * SizeOf(TImageSectionHeader)); + if Assigned(SectionHeaders) then try + if (SetFilePointer(FileHandle, SizeOfOptionalHeader - IMAGE_SIZEOF_NT_OPTIONAL_HEADER, nil, FILE_CURRENT) = INVALID_SET_FILE_POINTER) then + Exit; + if not(ReadFile(FileHandle, SectionHeaders^, NumberOfSections * SizeOf(TImageSectionHeader), BytesRead, nil) and (BytesRead = NumberOfSections * SizeOf(TImageSectionHeader))) then + Exit; + // Get RVA and size of the resource directory + with DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE] do begin + ResDirRVA := VirtualAddress; + ResDirSize := Size; + end; + // Search for section which contains the resource directory + ResDirRaw := 0; + for Section := 0 to NumberOfSections - 1 do + with SectionHeaders[Section] do + if (VirtualAddress <= ResDirRVA) and (VirtualAddress + SizeOfRawData >= ResDirRVA + ResDirSize) then begin + ResDirRaw := PointerToRawData - (VirtualAddress - ResDirRVA); + Break; + end; + // Resource directory table found? + if (ResDirRaw = 0) then + Exit; + // Read resource directory table + if (SetFilePointer(FileHandle, ResDirRaw, nil, FILE_BEGIN) = INVALID_SET_FILE_POINTER) then + Exit; + if not(ReadFile(FileHandle, ResDirTable, SizeOf(TImageResourceDirectory), BytesRead, nil) and (BytesRead = SizeOf(TImageResourceDirectory))) then + Exit; + // Convert from DosDateTime to SecondsSince1970 + if DosDateTimeToFileTime(HiWord(ResDirTable.TimeDateStamp), LoWord(ResDirTable.TimeDateStamp), FileTime) then begin + // FIXME: Borland's linker uses the local system time + // of the user who linked the executable image file. + // (is that information anywhere?) + TimeStamp := (ULARGE_INTEGER(FileTime).QuadPart - ULARGE_INTEGER(FileTime1970).QuadPart) div 10000000; + end; + finally + FreeMemory(SectionHeaders); + end; + end; + finally + CloseHandle(FileHandle); + end; + Result := UnixToDateTime(TimeStamp); +end;} + + +{function IsEmpty(Str: String): Boolean; +begin + // Alternative version of "Str = ''" + Result := Str = ''; +end;} + +{function IsNotEmpty(Str: String): Boolean; +begin + // Alternative version of "Str <> ''" + Result := Str <> ''; +end;} + + +{function MessageDialog(const Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons): Integer; +begin + Result := MessageDialog('', Msg, DlgType, Buttons); +end;} + + +{function MessageDialog(const Title, Msg: string; DlgType: TMsgDlgType; Buttons: TMsgDlgButtons; KeepAskingSetting: TAppSettingIndex=asUnused; FooterText: String=''): Integer; +var + m: String; + Dialog: TTaskDialog; + Btn: TTaskDialogButtonItem; + MsgButton: TMsgDlgBtn; + rx: TRegExpr; + KeepAskingValue: Boolean; + Hotkeys: String; + WebSearchUrl, WebSearchHost: String; + + procedure AddButton(BtnCaption: String; BtnResult: TModalResult; ResourceId: Integer=0); + var + i: Integer; + cap: String; + begin + Btn := TTaskDialogButtonItem(Dialog.Buttons.Add); + cap := ''; + if ResourceId > 0 then begin + // Prefer string from user32.dll + // May be empty on Wine! + cap := GetLocaleString(ResourceId) + end; + if cap.IsEmpty then begin + cap := _(BtnCaption); + for i:=1 to Length(cap) do begin + // Auto apply hotkey + if (Pos(LowerCase(cap[i]), Hotkeys) = 0) and cap[i].IsLetter then begin + Hotkeys := Hotkeys + LowerCase(cap[i]); + Insert('&', cap, i); + break; + end; + end; + end; + Btn.Caption := cap; + Btn.ModalResult := BtnResult; + if (DlgType = mtCriticalConfirmation) and (BtnResult = mrCancel) then + Btn.Default := True; + end; +begin + // Remember current path and restore it later, so the caller does not try to read from the wrong path after this dialog + AppSettings.StorePath; + + if (Win32MajorVersion >= 6) and StyleServices.Enabled then begin + // Use modern task dialog on Vista and above + Dialog := TTaskDialog.Create(nil); + Dialog.Flags := [tfEnableHyperlinks, tfAllowDialogCancellation]; + Dialog.CommonButtons := []; + if Assigned(MainForm) then + Dialog.OnHyperlinkClicked := MainForm.TaskDialogHyperLinkClicked; + + // Caption, title and text + case DlgType of + mtWarning: Dialog.Caption := _('Warning'); + mtError: Dialog.Caption := _('Error'); + mtInformation: Dialog.Caption := _('Information'); + mtConfirmation, mtCustom: Dialog.Caption := _('Confirm'); + end; + if Title <> Dialog.Caption then + Dialog.Title := Title; + if Assigned(MainForm) and (MainForm.ActiveConnection <> nil) then + Dialog.Caption := MainForm.ActiveConnection.Parameters.SessionName + ': ' + Dialog.Caption; + rx := TRegExpr.Create; + rx.Expression := 'https?://[^\s"]+'; + if ThemeIsDark then + Dialog.Text := Msg + else // See issue #2036 + Dialog.Text := rx.Replace(Msg, '<a href="$0">$0</a>', True); + rx.Free; + + // Main icon, and footer link + case DlgType of + mtWarning: + Dialog.MainIcon := tdiWarning; + mtError: begin + Dialog.MainIcon := tdiError; + WebSearchUrl := AppSettings.ReadString(asWebSearchBaseUrl); + WebSearchUrl := StringReplace(WebSearchUrl, '%q', EncodeURLParam(Copy(Msg, 1, 1000)), []); + rx := TRegExpr.Create; + rx.Expression := 'https?://(www\.)?([^/]+)/'; + if rx.Exec(WebSearchUrl) then + WebSearchHost := rx.Match[2] + else + WebSearchHost := '[unknown host]'; + rx.Free; + Dialog.FooterText := IfThen(FooterText.IsEmpty, '', FooterText + sLineBreak + sLineBreak) + + '<a href="'+WebSearchUrl+'">'+_('Find some help on this error')+' (=> '+WebSearchHost+')</a>'; + Dialog.FooterIcon := tdiInformation; + end; + mtInformation: + Dialog.MainIcon := tdiInformation; + mtConfirmation, mtCustom: begin + Dialog.Flags := Dialog.Flags + [tfUseHiconMain]; + Dialog.CustomMainIcon := ConfirmIcon; + end; + else + Dialog.MainIcon := tdiNone; + end; + + // Add buttons + for MsgButton in Buttons do begin + case MsgButton of + mbYes: AddButton('Yes', mrYes, 805); + mbNo: AddButton('No', mrNo, 806); + mbOK: AddButton('OK', mrOk, 800); + mbCancel: AddButton('Cancel', mrCancel, 801); + mbAbort: AddButton('Abort', mrAbort, 802); + mbRetry: AddButton('Retry', mrRetry, 803); + mbIgnore: AddButton('Ignore', mrIgnore, 804); + mbAll: AddButton('All', mrAll); + mbNoToAll: AddButton('No to all', mrNoToAll); + mbYesToAll: AddButton('Yes to all', mrYesToAll); + mbClose: AddButton('Close', mrClose, 807); + end; + end; + + // Checkbox, s'il vous plait? + KeepAskingValue := True; + if KeepAskingSetting <> asUnused then begin + if (not (mbNo in Buttons)) and (Buttons <> [mbOK]) then + raise Exception.CreateFmt(_('Missing "No" button in %() call'), ['MessageDialog']); + KeepAskingValue := AppSettings.ReadBool(KeepAskingSetting); + Dialog.Flags := Dialog.Flags + [tfVerificationFlagChecked]; + if Buttons = [mbOK] then + Dialog.VerificationText := _('Keep showing this dialog.') + else + Dialog.VerificationText := _('Keep asking this question.'); + end; + + // Supress dialog and assume "No" if user disabled this dialog + if KeepAskingValue then begin + Dialog.Execute; + Result := Dialog.ModalResult; + if (KeepAskingSetting <> asUnused) and (not (tfVerificationFlagChecked in Dialog.Flags)) then + AppSettings.WriteBool(KeepAskingSetting, False); + end else + Result := mrNo; + + Dialog.Free; + end else begin + // Backwards compatible dialog on Windows XP + m := Msg; + if not Title.IsEmpty then + m := Title + SLineBreak + SLineBreak + m; + if not FooterText.IsEmpty then + m := m + SLineBreak + SLineBreak + FooterText; + + if KeepAskingSetting <> asUnused then + KeepAskingValue := AppSettings.ReadBool(KeepAskingSetting) + else + KeepAskingValue := True; + + if KeepAskingValue then + Result := MessageDlg(m, DlgType, Buttons, 0) + else + Result := mrNo; + end; + + AppSettings.RestorePath; +end;} + + +{function ErrorDialog(Msg: string): Integer; +begin + Result := MessageDialog('', Msg, mtError, [mbOK]); +end;} + + +{function ErrorDialog(const Title, Msg: string): Integer; +begin + Result := MessageDialog(Title, Msg, mtError, [mbOK]); +end;} + + +{function GetLocaleString(const ResourceId: Integer): WideString; +var + Buffer: WideString; + BufferLen: Integer; +begin + Result := ''; + if LibHandleUser32 <> 0 then begin + SetLength(Buffer, 255); + BufferLen := LoadStringW(LibHandleUser32, ResourceId, PWideChar(Buffer), Length(Buffer)); + if BufferLen <> 0 then + Result := Copy(Buffer, 1, BufferLen); + end; +end;} + + +{function GetHTMLCharsetByEncoding(Encoding: TEncoding): String; +begin + Result := ''; + if Encoding = TEncoding.Default then + Result := 'Windows-'+IntToStr(GetACP) + else if Encoding.CodePage = 437 then + Result := 'ascii' + else if Encoding = TEncoding.Unicode then + Result := 'utf-16le' + else if Encoding = TEncoding.BigEndianUnicode then + Result := 'utf-16' + else if Encoding = TEncoding.UTF8 then + Result := 'utf-8' + else if Encoding = TEncoding.UTF7 then + Result := 'utf-7'; +end;} + + +{procedure ParseCommandLine(CommandLine: String; var ConnectionParams: TConnectionParameters; var FileNames: TStringList; var RunFrom: String); +var + rx: TRegExpr; + ExeName, SessName, Host, Lib, Port, User, Pass, Socket, AllDatabases, + SSLPrivateKey, SSLCACertificate, SSLCertificate, SSLCipher: String; + NetType, WindowsAuth, WantSSL, CleartextPluginEnabled, SSLVerification: Integer; + AbsentFiles: TStringList; + + function GetParamValue(ShortName, LongName: String): String; + begin + // Return one command line switch. Doublequotes are not mandatory. + Result := ''; + rx.Expression := '\s(\-'+ShortName+'|\-\-'+LongName+')\s*\=?\s*\"([^\-][^\"]*)\"'; + if rx.Exec(CommandLine) then + Result := rx.Match[2] + else begin + rx.Expression := '\s(\-'+ShortName+'|\-\-'+LongName+')\s*\=?\s*([^\-]\S*)'; + if rx.Exec(CommandLine) then + Result := rx.Match[2]; + end; + end; + + procedure GetFileNames(Expression: String); + begin + rx.Expression := Expression; + if rx.Exec(CommandLine) then while true do begin + if FileExists(rx.Match[1]) then + FileNames.Add(rx.Match[1]) + else + AbsentFiles.Add(rx.Match[1]); + // Remove match from input string, so the second call to GetFileNames without quotes + // does not detect filenames cut at whitespace + Delete(CommandLine, rx.MatchPos[1], rx.MatchLen[1]); + if not rx.ExecNext then + break; + end; + end; + +begin + // Parse command line, probably sent by blocked second application instance. + // Try to build connection parameters out of it. + SessName := ''; + FileNames := TStringList.Create; + AbsentFiles := TStringList.Create; + + // Add leading (and trailing) space, so the regular expressions can request a mandantory space + // before (and after) each param (and filename) including the first one (and last one) + ExeName := ExtractFileName(ParamStr(0)); + CommandLine := Copy(CommandLine, Pos(ExeName, CommandLine)+Length(ExeName), Length(CommandLine)); + CommandLine := CommandLine + ' '; + rx := TRegExpr.Create; + + // --runfrom=scheduler after build update + RunFrom := GetParamValue('rf', 'runfrom'); + + SessName := GetParamValue('d', 'description'); + if SessName <> '' then begin + try + ConnectionParams := TConnectionParameters.Create(SessName); + except + on E:Exception do begin + // Session params not found in registry + MainForm.LogSQL(E.Message); + end; + end; + end; + + // Test if params were passed. If given, override previous values loaded from registry. + // Enables the user to log into a session with a different, non-stored user: -dSession -uSomeOther + NetType := StrToIntDef(GetParamValue('n', 'nettype'), 0); + Host := GetParamValue('h', 'host'); + Lib := GetParamValue('l', 'library'); + User := GetParamValue('u', 'user'); + Pass := GetParamValue('p', 'password'); + CleartextPluginEnabled := StrToIntDef(GetParamValue('cte', 'cleartextenabled'), -1); + Socket := GetParamValue('S', 'socket'); + Port := GetParamValue('P', 'port'); + AllDatabases := GetParamValue('db', 'databases'); + WindowsAuth := StrToIntDef(GetParamValue('W', 'winauth'), -1); + WantSSL := StrToIntDef(GetParamValue('ssl', 'ssl'), -1); + SSLPrivateKey := GetParamValue('sslpk', 'sslprivatekey'); + SSLCACertificate := GetParamValue('sslca', 'sslcacertificate'); + SSLCertificate := GetParamValue('sslcert', 'sslcertificate'); + SSLCipher := GetParamValue('sslcip', 'sslcipher'); + SSLVerification := StrToIntDef(GetParamValue('sslvrf', 'sslverification'), -1); + // Leave out support for startup script, seems reasonable for command line connecting + + if (Host <> '') or (User <> '') or (Pass <> '') or (Port <> '') or (Socket <> '') or (AllDatabases <> '') then begin + if not Assigned(ConnectionParams) then begin + ConnectionParams := TConnectionParameters.Create; + ConnectionParams.SessionPath := SessName; + end; + if NetType <> 0 then ConnectionParams.NetType := TNetType(NetType); + try + ConnectionParams.GetNetTypeGroup; + except + ConnectionParams.NetType := ntMySQL_TCPIP; + end; + if Host <> '' then ConnectionParams.Hostname := Host; + if Lib <> '' then ConnectionParams.LibraryOrProvider := Lib; + if ConnectionParams.LibraryOrProvider.IsEmpty then ConnectionParams.LibraryOrProvider := ConnectionParams.DefaultLibrary; + if User <> '' then ConnectionParams.Username := User; + if Pass <> '' then ConnectionParams.Password := Pass; + if CleartextPluginEnabled in [0,1] then + ConnectionParams.CleartextPluginEnabled := Boolean(CleartextPluginEnabled); + if Port <> '' then ConnectionParams.Port := StrToIntDef(Port, 0); + if Socket <> '' then begin + ConnectionParams.Hostname := Socket; + ConnectionParams.NetType := ntMySQL_NamedPipe; + end; + if AllDatabases <> '' then ConnectionParams.AllDatabasesStr := AllDatabases; + if WantSSL in [0,1] then + ConnectionParams.WantSSL := Boolean(WantSSL); + if SSLPrivateKey <> '' then + ConnectionParams.SSLPrivateKey := SSLPrivateKey; + if SSLCACertificate <> '' then + ConnectionParams.SSLCACertificate := SSLCACertificate; + if SSLCertificate <> '' then + ConnectionParams.SSLCertificate := SSLCertificate; + if SSLCipher <> '' then + ConnectionParams.SSLCipher := SSLCipher; + if SSLVerification >= 0 then + ConnectionParams.SSLVerification := SSLVerification; + + if WindowsAuth in [0,1] then + ConnectionParams.WindowsAuth := Boolean(WindowsAuth); + + // Ensure we have a session name to pass to InitConnection + if (ConnectionParams.SessionPath = '') and (ConnectionParams.Hostname <> '') then + ConnectionParams.SessionPath := ConnectionParams.Hostname; + end; + + // Check for valid filename(s) in parameters. + // We support doublequoted and unquoted parameters. + GetFileNames('\"([^\"]+\.sql)\"'); + GetFileNames('\s([^\s\"]+\.sql)\b'); + if AbsentFiles.Count > 0 then + ErrorDialog(_('Could not load file(s):'), AbsentFiles.Text); + AbsentFiles.Free; + + rx.Free; +end;} + + +function _(const Pattern: string): string; +begin + // gnugettext not working yet... + Result := Pattern; +end; + + +function f_(const Pattern: string; const Args: array of const): string; +var + TranslatedPattern: String; +begin + // Helper for translation, replacement for Format(_()) + try + TranslatedPattern := Pattern; //_(Pattern); + Result := Format(TranslatedPattern, Args); + except + on E:Exception do begin + MainForm.LogSQL(E.ClassName+' in translation string with invalid format arguments: "'+TranslatedPattern+'"', lcError); + Result := Format(Pattern, Args); + end; + end; +end; + + +{function GetOutputFilename(FilenameWithPlaceholders: String; DBObj: TDBObject): String; +var + Arguments: TExtStringList; + Year, Month, Day, Hour, Min, Sec, MSec: Word; + i: Integer; +begin + // Rich format output filename, replace certain markers. See issue #2622 + Arguments := TExtStringList.Create; + + if Assigned(DBObj) then begin + Arguments.Values['session'] := ValidFilename(DBObj.Connection.Parameters.SessionName); + Arguments.Values['host'] := ValidFilename(DBObj.Connection.Parameters.Hostname); + Arguments.Values['u'] := ValidFilename(DBObj.Connection.Parameters.Username); + Arguments.Values['db'] := ValidFilename(DBObj.Database); + end; + Arguments.Values['date'] := ValidFilename(DateTimeToStr(Now)); + DecodeDateTime(Now, Year, Month, Day, Hour, Min, Sec, MSec); + Arguments.Values['d'] := Format('%.2d', [Day]); + Arguments.Values['m'] := Format('%.2d', [Month]); + Arguments.Values['y'] := Format('%.4d', [Year]); + Arguments.Values['h'] := Format('%.2d', [Hour]); + Arguments.Values['i'] := Format('%.2d', [Min]); + Arguments.Values['s'] := Format('%.2d', [Sec]); + + Result := FilenameWithPlaceholders; + for i:=0 to Arguments.Count-1 do begin + Result := StringReplace(Result, '%'+Arguments.Names[i], Arguments.ValueFromIndex[i], [rfReplaceAll]); + end; + Arguments.Free; +end;} + + +{function GetOutputFilenamePlaceholders: TStringList; +begin + // Return a list with valid placeholder=>description pairs + Result := TStringList.Create; + Result.Values['session'] := _('Session name'); + Result.Values['host'] := _('Hostname'); + Result.Values['u'] := _('Username'); + Result.Values['db'] := _('Database'); + Result.Values['date'] := _('Date and time'); + Result.Values['d'] := _('Day of month'); + Result.Values['m'] := _('Month'); + Result.Values['y'] := _('Year'); + Result.Values['h'] := _('Hour'); + Result.Values['i'] := _('Minute'); + Result.Values['s'] := _('Second'); +end;} + + +{function GetSystemImageList: TImageList; +var + Info: TSHFileInfo; + ImageListHandle: Cardinal; +begin + // Create shared imagelist once and use in TPopupMenu and TVirtualTree or whatever + if SystemImageList = nil then begin + ImageListHandle := SHGetFileInfo('', 0, Info, SizeOf(Info), SHGFI_SYSICONINDEX or SHGFI_SMALLICON); + if ImageListHandle <> 0 then begin + SystemImageList := TImageList.Create(MainForm); + SystemImageList.Handle := ImageListHandle; + SystemImageList.ShareImages := true; + SystemImageList.DrawingStyle := dsTransparent; + end; + end; + Result := SystemImageList; +end;} + + +{function GetSystemImageIndex(Filename: String): Integer; +var + Info: TSHFileInfo; +begin + // Return image index of shared system image list, for a given filename + SHGetFileInfo(PChar(Filename), 0, Info, SizeOf(Info), SHGFI_SYSICONINDEX or SHGFI_TYPENAME); + Result := Info.iIcon; +end;} + + +{function GetExecutableBits: Byte; +begin} + //{$IFDEF WIN64} + //Result := 64; + //{$ELSE} + //Result := 32; + //{$ENDIF} +//end; + + +{procedure Help(Sender: TObject; Anchor: String); +var + Place: String; +begin + // Go to online help page + if Sender is TAction then + Place := (Sender as TAction).ActionComponent.Name + else if Sender is TControl then + Place := (Sender as TControl).Name + else + Place := 'unhandled-'+Sender.ClassName; + if not Anchor.IsEmpty then + Anchor := '#'+Anchor; + ShellExec(APPDOMAIN+'help.php?place='+EncodeURLParam(Place)+Anchor); +end;} + + +{function PortOpen(Port: Word): Boolean; +var + client: sockaddr_in; + sock: Integer; + ret: Integer; + wsdata: WSAData; +begin + Result := True; + ret := WSAStartup($0002, wsdata); + if ret<>0 then + Exit; + try + client.sin_family := AF_INET; + client.sin_port := htons(Port); + client.sin_addr.s_addr := inet_addr(PAnsiChar('127.0.0.1')); + sock := socket(AF_INET, SOCK_STREAM, 0); + Result := connect(sock, client, SizeOf(client)) <> 0; + finally + WSACleanup; + end; +end;} + + +{function IsValidFilePath(FilePath: String): Boolean; +var + Pieces: TStringList; + i: Integer; +begin + // Check file path for invalid characters. See http://www.heidisql.com/forum.php?t=20873 + Result := True; + Pieces := TStringList.Create; + SplitRegExpr('[\\\/]', FilePath, Pieces); + for i:=1 to Pieces.Count-1 do begin + Result := Result and TPath.HasValidFileNameChars(Pieces[i], False); + end; + Pieces.Free; +end;} + + +{function FileIsWritable(FilePath: String): Boolean; +var + hFile: DWORD; +begin + // Check if file is writable + if not FileExists(FilePath) then begin + // Return true if file does not exist + Result := True; + end else begin + hFile := CreateFile(PChar(FilePath), GENERIC_WRITE, 0, nil, OPEN_EXISTING, 0, 0); + Result := hFile <> INVALID_HANDLE_VALUE; + CloseHandle(hFile); + end; +end;} + + +{function GetThemeColor(Color: TColor): TColor; +begin + // Not required with vcl-style-utils: + // Result := TStyleManager.ActiveStyle.GetSystemColor(Color); + Result := Color; +end;} + + +{function ThemeIsDark(ThemeName: String=''): Boolean; +const + DarkThemes: String = 'Amakrits,Aqua Graphite,Auric,Carbon,Charcoal Dark Slate,Cobalt XEMedia,Glossy,Glow,Golden Graphite,Material,Onyx Blue,Ruby Graphite,TabletDark,Windows10 Dark,Windows10 SlateGray'; +var + DarkThemesList: TStringList; +begin + DarkThemesList := Explode(',', DarkThemes); + if ThemeName.IsEmpty then + ThemeName := TStyleManager.ActiveStyle.Name; + Result := DarkThemesList.IndexOf(ThemeName) > -1; + DarkThemesList.Free; +end;} + + +{function ProcessExists(pid: Cardinal; ExeNamePattern: String): Boolean; +var + Proc: TProcessEntry32; + SnapShot: THandle; + ContinueLoop: Boolean; +begin + // Check if a given process id exists + SnapShot := CreateToolhelp32Snapshot(TH32CS_SnapProcess, 0); + Proc.dwSize := Sizeof(Proc); + Result := False; + ContinueLoop := Process32First(SnapShot, Proc); + while ContinueLoop do begin + Result := (Proc.th32ProcessID = pid) and ContainsText(Proc.szExeFile, ExeNamePattern); + if Result then + Break; + ContinueLoop := Process32Next(SnapShot, Proc); + end; + CloseHandle(Snapshot); +end;} + + +{procedure ToggleCheckBoxWithoutClick(chk: TCheckBox; State: Boolean); +var + ClickEvent: TNotifyEvent; +begin + ClickEvent := chk.OnClick; + chk.OnClick := nil; + chk.Checked := State; + chk.OnClick := ClickEvent; +end;} + + +{function SynCompletionProposalPrettyText(ImageIndex: Integer; LeftText, CenterText, RightText: String; + LeftColor: TColor=-1; CenterColor: TColor=-1; RightColor: TColor=-1): String; +const} +// LineFormat = '\image{%d}\hspace{5}\color{%s}%s\column{}\color{%s}%s\hspace{10}\color{%s}\style{+i}%s'; +{begin + // Return formatted item string for a TSynCompletionProposal + if LeftColor = -1 then LeftColor := clGrayText; + if CenterColor = -1 then CenterColor := clWindowText; + if RightColor = -1 then RightColor := clGrayText; + Result := Format(LineFormat, [ImageIndex, ColorToString(LeftColor), LeftText, ColorToString(CenterColor), CenterText, ColorToString(RightColor), RightText]); +end;} + + +{function PopupComponent(Sender: TObject): TComponent; +var + Menu: TObject; +begin + // Return owner component of clicked menu item, probably combined with a TAction + Result := nil; + Menu := nil; + if Sender is TAction then + Sender := (Sender as TAction).ActionComponent; + + if Sender is TMenuItem then + Menu := (Sender as TMenuItem).GetParentMenu + else if Sender is TPopupMenu then + Menu := Sender; + + if Menu is TPopupMenu then + Result := (Menu as TPopupMenu).PopupComponent; +end;} + + +{function IsWine: Boolean; +var + NTHandle: THandle; + wine_nt_to_unix_file_name: procedure(p1:pointer; p2:pointer); stdcall; +begin + // Detect if we're running on Wine, not on native Windows + // Idea taken from http://ruminatedrumblings.blogspot.com/2008/04/detecting-virtualized-environment.html + if IsWineStored = -1 then begin + NTHandle := LoadLibrary('NTDLL.DLL'); + if NTHandle>32 then + wine_nt_to_unix_file_name := GetProcAddress(NTHandle, 'wine_nt_to_unix_file_name') + else + wine_nt_to_unix_file_name := nil; + IsWineStored := IfThen(Assigned(wine_nt_to_unix_file_name), 1, 0); + FreeLibrary(NTHandle); + end; + Result := IsWineStored = 1; +end;} + + +{function DirSep: Char; +begin + if IsWine then + Result := '/' + else + Result := '\'; +end;} + +{procedure FindComponentInstances(BaseForm: TComponent; ClassType: TClass; var List: TObjectList); +var + i: Integer; +begin + for i:=0 to BaseForm.ComponentCount-1 do begin + if BaseForm.Components[i] is ClassType then + List.Add(BaseForm.Components[i] as ClassType) + else + FindComponentInstances(BaseForm.Components[i], ClassType, List); + end; +end;} + +{function WebColorStrToColorDef(WebColor: string; Default: TColor): TColor; +begin + try + Result := WebColorStrToColor(WebColor); + except + Result := Default; + end; +end;} + + +{function UserAgent(OwnerComponent: TComponent): String; +var + OS: String; +begin + if IsWine then + OS := 'Linux/Wine' + else + OS := 'Windows NT '+IntToStr(Win32MajorVersion)+'.'+IntToStr(Win32MinorVersion); + Result := APPNAME+'/'+MainForm.AppVersion+' ('+OS+'; '+ExtractFilename(Application.ExeName)+'; '+OwnerComponent.Name+')'; +end;} + + +{function CodeIndent(Steps: Integer=1): String; +begin + // Provide tab or spaces for indentation, uniquely used for all SQL statements + if AppSettings.ReadBool(asTabsToSpaces) then + Result := StringOfChar(' ', AppSettings.ReadInt(asTabWidth) * Steps) + else + Result := StringOfChar(#9, Steps); +end;} + + +{function EscapeHotkeyPrefix(Text: String): String; +begin + // Issue #1992: Escape ampersand in caption of menus and tabs, preventing underlined hotkey generation + Result := StringReplace(Text, Vcl.Menus.cHotkeyPrefix, Vcl.Menus.cHotkeyPrefix + Vcl.Menus.cHotkeyPrefix, [rfReplaceAll]); +end;} + + +{ Get SID of current Windows user, probably useful in the future +{function GetCurrentUserSID: string; +type + PTOKEN_USER = ^TOKEN_USER; + _TOKEN_USER = record + User: TSidAndAttributes; + end; + TOKEN_USER = _TOKEN_USER; +var + hToken: THandle; + cbBuf: Cardinal; + ptiUser: PTOKEN_USER; + bSuccess: Boolean; + StrSid: PWideChar; +begin + // Taken from https://stackoverflow.com/a/71730865/4110077 + // SidToString does not exist, prefer WinApi.Windows.ConvertSidToStringSid() + Result := ''; + + // Get the calling thread's access token. + if not OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, True, hToken) then + begin + if (GetLastError <> ERROR_NO_TOKEN) then + Exit; + + // Retry against process token if no thread token exists. + if not OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, hToken) then + Exit; + end; + try + // Obtain the size of the user information in the token. + bSuccess := GetTokenInformation(hToken, TokenUser, nil, 0, cbBuf); + ptiUser := nil; + try + while (not bSuccess) and (GetLastError = ERROR_INSUFFICIENT_BUFFER) do + begin + ReallocMem(ptiUser, cbBuf); + bSuccess := GetTokenInformation(hToken, TokenUser, ptiUser, cbBuf, cbBuf); + end; + ConvertSidToStringSid(ptiUser.User.Sid, StrSid); + Result := StrSid; + finally + FreeMem(ptiUser); + end; + finally + CloseHandle(hToken); + end; +end;} + + +{ Threading stuff } + +{constructor TQueryThread.Create(Connection: TDBConnection; Batch: TSQLBatch; TabNumber: Integer); +begin + inherited Create(False); + FConnection := Connection; + FAborted := False; + FBatch := Batch; + FTabNumber := TabNumber; + FBatchPosition := 0; + FQueryStartedAt := Now; + FQueryTime := 0; + FQueryNetTime := 0; + FRowsAffected := 0; + FRowsFound := 0; + FWarningCount := 0; + FErrorMessage := ''; + FBatchInOneGo := MainForm.actBatchInOneGo.Checked; + FStopOnErrors := MainForm.actQueryStopOnErrors.Checked; + FreeOnTerminate := True; + Priority := tpNormal; +end;} + + +{procedure TQueryThread.Execute; +var + SQL: String; + i, BatchStartOffset, ResultCount: Integer; + PacketSize, MaxAllowedPacket: Int64; + DoStoreResult, ErrorAborted, LogMaxResultsDone: Boolean; +begin + inherited; + + MaxAllowedPacket := 0; + i := 0; + ResultCount := 0; + ErrorAborted := False; + LogMaxResultsDone := False; + + while i < FBatch.Count do begin + SQL := ''; + if not FBatchInOneGo then begin + SQL := FBatch[i].SQL; + Inc(i); + end else begin + // Concat queries up to a size of max_allowed_packet + if MaxAllowedPacket = 0 then begin + FConnection.SetLockedByThread(Self); + MaxAllowedPacket := FConnection.MaxAllowedPacket; + FConnection.SetLockedByThread(nil); + // TODO: Log('Detected maximum allowed packet size: '+FormatByteNumber(MaxAllowedPacket), lcDebug); + end; + BatchStartOffset := FBatch[i].LeftOffset; + while i < FBatch.Count do begin + PacketSize := FBatch[i].RightOffset - BatchStartOffset + ((i-FBatchPosition) * 20); + if not SQL.IsEmpty then begin + if PacketSize >= MaxAllowedPacket then begin + // TODO: Log('Limiting batch packet size to '+FormatByteNumber(Length(SQL))+' with '+FormatNumber(i-FUserQueryOffset)+' queries.', lcDebug); + Break; + end + else begin + // Don't append to the very last query. See issue #1583 + SQL := SQL + '; '; + end; + end; + SQL := SQL + FBatch[i].SQL; + Inc(i); + end; + FQueriesInPacket := i - FBatchPosition; + end; + Synchronize(procedure begin MainForm.BeforeQueryExecution(Self); end); + try + FConnection.SetLockedByThread(Self); + DoStoreResult := ResultCount < AppSettings.ReadInt(asMaxQueryResults); + if (not DoStoreResult) and (not LogMaxResultsDone) then begin + // Inform user about preference setting for limiting result tabs + FConnection.Log(lcInfo, + f_('Reached maximum number of result tabs (%d). To display more results, increase setting in Preferences > SQL', [AppSettings.ReadInt(asMaxQueryResults)]) + ); + LogMaxResultsDone := True; + end; + FConnection.Query(SQL, DoStoreResult, lcUserFiredSQL); + Inc(ResultCount, FConnection.ResultCount); + FBatchPosition := i; + Inc(FQueryTime, FConnection.LastQueryDuration); + Inc(FQueryNetTime, FConnection.LastQueryNetworkDuration); + Inc(FRowsAffected, FConnection.RowsAffected); + Inc(FRowsFound, FConnection.RowsFound); + Inc(FWarningCount, FConnection.WarningCount); + except + on E:EDbError do begin + if FStopOnErrors or (i = FBatch.Count - 1) then begin + FErrorMessage := E.Message; + ErrorAborted := True; + end; + end; + end; + FConnection.SetLockedByThread(nil); + Synchronize(procedure begin MainForm.AfterQueryExecution(Self); end); + FConnection.ShowWarnings; + // Check if FAborted is set by the main thread, to avoid proceeding the loop in case + // FStopOnErrors is set to false + if FAborted or ErrorAborted then + break; + end; + + Synchronize(procedure begin MainForm.FinishedQueryExecution(Self); end); +end;} + + +{procedure TQueryThread.LogFromThread(Msg: String; Category: TDBLogCategory); +begin + Queue(procedure begin FConnection.Log(Category, Msg); end); +end;} + + +{ TSQLSentence } + +{constructor TSQLSentence.Create(Owner: TSQLBatch); +begin + // Use a back reference to the parent batch object, so we can extract SQL from it + FOwner := Owner; +end;} + + +{function TSQLSentence.GetSize: Integer; +begin + Result := RightOffset - LeftOffset; +end;} + + +{function TSQLSentence.GetSQL: String; +begin + // Result := Copy(FOwner.SQL, LeftOffset, RightOffset-LeftOffset); + // Probably faster than Copy(): + SetString(Result, PChar(FOwner.SQL) +LeftOffset -1, RightOffset-LeftOffset); +end;} + + +{function TSQLSentence.GetSQLWithoutComments: String; +begin + Result := FOwner.GetSQLWithoutComments(GetSQL); +end;} + + +{ TSQLBatch } + +{function TSQLBatch.GetSize: Integer; +var + Query: TSQLSentence; +begin + // Return overall string length of batch + Result := 0; + for Query in Self do + Inc(Result, Query.Size); +end;} + + +{procedure TSQLBatch.SetSQL(Value: String); +var + i, AllLen, DelimLen, DelimStart, LastLeftOffset, RightOffset: Integer; + c, n, LastStringEncloser: Char; + Delim, DelimTest, QueryTest: String; + InString, InComment, InBigComment, InEscape: Boolean; + Marker: TSQLSentence; + rx: TRegExpr; +const + StringEnclosers = ['"', '''', '`']; + NewLines = [#13, #10]; + WhiteSpaces = NewLines + [#9, ' ']; +begin + // Scan SQL batch for delimiters and store a list with start + end offsets + FSQL := Value; + Clear; + AllLen := Length(FSQL); + i := 0; + LastLeftOffset := 1; + Delim := Mainform.Delimiter; + InString := False; // Loop in "enclosed string" or `identifier` + InComment := False; // Loop in one-line comment (# or --) + InBigComment := False; // Loop in /* multi-line */ or /*! condictional comment */ + InEscape := False; // Previous char was backslash + LastStringEncloser := #0; + DelimLen := Length(Delim); + rx := TRegExpr.Create; + rx.Expression := '^\s*DELIMITER\s+(\S+)'; + rx.ModifierG := True; + rx.ModifierI := True; + rx.ModifierM := False; + while i < AllLen do begin + Inc(i); + // Current and next char + c := FSQL[i]; + if i < AllLen then n := FSQL[i+1] + else n := #0; + + // Check for comment syntax and for enclosed literals, so a query delimiter can be ignored + if (not InComment) and (not InBigComment) and (not InString) and ((c + n = '--') or (c = '#')) then + InComment := True; + if (not InComment) and (not InBigComment) and (not InString) and (c + n = '/*') then + InBigComment := True; + if InBigComment and (not InComment) and (not InString) and (c + n = '*/') then + InBigComment := False; + if (not InEscape) and (not InComment) and (not InBigComment) and CharInSet(c, StringEnclosers) then begin + if (not InString) or (InString and (c = LastStringEncloser)) then begin + InString := not InString; + LastStringEncloser := c; + end; + end; + if (CharInSet(c, NewLines) and (not CharInSet(n, NewLines))) or (i = 1) then begin + if i > 1 then + InComment := False; + if (not InString) and (not InBigComment) and rx.Exec(copy(FSQL, i, 100)) then begin + Delim := rx.Match[1]; + DelimLen := rx.MatchLen[1]; + Inc(i, rx.MatchLen[0]); + LastLeftOffset := i; + continue; + end; + end; + if not InEscape then + InEscape := c = '\' + else + InEscape := False; + + // Prepare delimiter test string + if (not InComment) and (not InString) and (not InBigComment) then begin + DelimStart := Max(1, i+1-DelimLen); + DelimTest := Copy(FSQL, DelimStart, i-Max(i-DelimLen, 0)); + end else + DelimTest := ''; + + // End of query or batch reached. Add query markers to result list if sentence is not empty. + if (DelimTest = Delim) or (i = AllLen) then begin + RightOffset := i+1; + if DelimTest = Delim then + Dec(RightOffset, DelimLen); + QueryTest := Trim(Copy(FSQL, LastLeftOffset, RightOffset-LastLeftOffset)); + if (QueryTest <> '') and (QueryTest <> Delim) then begin + Marker := TSQLSentence.Create(Self); + while CharInSet(FSQL[LastLeftOffset], WhiteSpaces) do + Inc(LastLeftOffset); + Marker.LeftOffset := LastLeftOffset; + Marker.RightOffset := RightOffset; + Add(Marker); + LastLeftOffset := i+1; + end; + end; + end; +end;} + +{function TSQLBatch.GetSQLWithoutComments: String; +begin + Result := GetSQLWithoutComments(SQL); +end;} + +{class function TSQLBatch.GetSQLWithoutComments(FullSQL: String): String; +var + InLineComment, InMultiLineComment: Boolean; + AddCur: Boolean; + i: Integer; + Cur, Prev1, Prev2: Char; +begin + // Strip comments out of SQL sentence + // TODO: leave quoted string literals and identifiers untouched + Result := ''; + InLineComment := False; + InMultiLineComment := False; + Prev1 := #0; + Prev2 := #0; + for i:=1 to Length(FullSQL) do begin + Cur := FullSQL[i]; + AddCur := True; + if i > 1 then Prev1 := FullSQL[i-1]; + if i > 2 then Prev2 := FullSQL[i-2]; + + if (Cur = '*') and (Prev1 = '/') then begin + InMultiLineComment := True; + System.Delete(Result, Length(Result), 1); // Delete comment chars + end + else if InMultiLineComment and (Cur = '/') and (Prev1 = '*') then begin + InMultiLineComment := False; + System.Delete(Result, Length(Result), 1); + AddCur := False; + end; + + if not InMultiLineComment then begin + if InLineComment and ((Cur = #13) or (Cur = #10)) then begin + InLineComment := False; // Reset + end + else if Cur = '#' then begin + InLineComment := True; + end + else if (Cur = ' ') and (Prev1 = '-') and (Prev2 = '-') then begin + InLineComment := True; + System.Delete(Result, Length(Result)-1, 2); // Delete comment chars + end; + end; + + if AddCur and (not InLineComment) and (not InMultiLineComment) then begin + Result := Result + Cur; + end; + end; +end;} + +{ THttpDownload } + +{constructor THttpDownload.Create(Owner: TComponent); +begin + FBytesRead := -1; + FContentLength := -1; + FOwner := Owner; + FTimeOut := 10; +end;} + + +{procedure THttpDownload.SendRequest(Filename: String); +var + NetHandle: HINTERNET; + UrlHandle: HINTERNET; + Buffer: array[1..4096] of AnsiChar; + Head: array[1..1024] of Char; + BytesInChunk, HeadSize, Reserved, TimeOutSeconds: Cardinal; + LocalFile: File; + DoStore: Boolean; + HttpStatus: Integer; + ContentChunk: UTF8String; +begin + DoStore := False; + NetHandle := InternetOpen(PChar(UserAgent(FOwner)), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0); + + // Do not let the user wait 30s + TimeOutSeconds := FTimeOut * 1000; + InternetSetOption(NetHandle, INTERNET_OPTION_CONNECT_TIMEOUT, @TimeOutSeconds, SizeOf(TimeOutSeconds)); + + UrlHandle := nil; + FLastContent := ''; + try + UrlHandle := InternetOpenURL(NetHandle, PChar(FURL), nil, 0, INTERNET_FLAG_RELOAD, 0); + if (not Assigned(UrlHandle)) and FURL.StartsWith('https:', true) then begin + // Try again without SSL. See issue #65 and #1209 + MainForm.LogSQL(f_('Could not open %s (%s) - trying again without SSL...', [FURL, SysErrorMessage(GetLastError)]), lcError); + FURL := ReplaceRegExpr('^https:', FURL, 'http:'); + UrlHandle := InternetOpenURL(NetHandle, PChar(FURL), nil, 0, INTERNET_FLAG_RELOAD, 0); + end; + if not Assigned(UrlHandle) then begin + raise Exception.CreateFmt(_('Could not open %s (%s)'), [FURL, SysErrorMessage(GetLastError)]); + end; + + // Detect content length + HeadSize := SizeOf(Head); + Reserved := 0; + if HttpQueryInfo(UrlHandle, HTTP_QUERY_CONTENT_LENGTH, @Head, HeadSize, Reserved) then + FContentLength := StrToIntDef(Head, -1) + else + raise Exception.CreateFmt(_('Server did not send required "Content-Length" header: %s'), [FURL]); + + // Check if we got HTTP status 200 + HeadSize := SizeOf(Head); + Reserved := 0; + if HttpQueryInfo(UrlHandle, HTTP_QUERY_STATUS_CODE, @Head, HeadSize, Reserved) then begin + HttpStatus := StrToIntDef(Head, -1); + if HttpStatus <> 200 then + raise Exception.CreateFmt(_('Got HTTP status %d from %s'), [HttpStatus, FURL]); + end; + + // Create local file + if Filename <> '' then begin + AssignFile(LocalFile, FileName); + Rewrite(LocalFile, 1); + DoStore := True; + end; + + // Stream contents + while true do begin + InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesInChunk); + // Either store as file or in memory variable + if DoStore then begin + BlockWrite(LocalFile, Buffer, BytesInChunk) + end else begin + SetString(ContentChunk, PAnsiChar(@Buffer[1]), BytesInChunk); + FLastContent := FLastContent + String(ContentChunk); + end; + Inc(FBytesRead, BytesInChunk); + if Assigned(FOnProgress) then + FOnProgress(Self); + if BytesInChunk = 0 then + break; + end; + + finally + if DoStore then + CloseFile(LocalFile); + if Assigned(UrlHandle) then + InternetCloseHandle(UrlHandle); + if Assigned(NetHandle) then + InternetCloseHandle(NetHandle); + end; +end;} + + + +{ TExtStringList } +// taken from https://stackoverflow.com/questions/33893377/can-i-prevent-tstringlist-removing-key-value-pair-when-value-set-to-empty + +{function TExtStringList.GetValue(const Name: string): string; +begin + Result := Self.GetValue(Name); +end;} + + +{procedure TExtStringList.SetValue(const Name, Value: string); +var + I: Integer; +begin + I := IndexOfName(Name); + if I < 0 then I := Add(''); + Put(I, Name + NameValueSeparator + Value); +end;} + + +{ TSqlTranspiler } + +{class function TSqlTranspiler.CreateTable(SQL: String; SourceDb, TargetDb: TDBConnection): String; +begin + Result := SQL; + + if SourceDb.Parameters.IsMySQL(False) and TargetDb.Parameters.IsMariaDB then begin + // Remove COLLATE clause from virtual column definition: + // `tax_status` varchar(255) COLLATE utf8mb4_unicode_ci GENERATED ALWAYS AS (json_unquote(json_extract(`price`,'$.taxStatus'))) VIRTUAL + Result := ReplaceRegExpr('\sCOLLATE\s\w+(\s+GENERATED\s)', Result, '$1', [rroModifierI, rroUseSubstitution]); + end; + +end;} + + +{ TClipboardHelper } + +{function TClipboardHelper.GetTryAsText: String; +var + AttemptsLeft: Integer; + Success: Boolean; + LastError: String; +begin + AttemptsLeft := 5; + Result := ''; + Success := False; + while AttemptsLeft > 0 do begin + Dec(AttemptsLeft); + try + Result := AsText; + Success := True; + Break; + except + // We could also just catch EClipboardException + on E:Exception do begin + LastError := E.Message; + Sleep(100); + end; + end; + end; + if not Success then + MainForm.LogSQL(LastError, lcError); +end;} + +{procedure TClipboardHelper.SetTryAsText(AValue: String); +var + AttemptsLeft: Integer; + Success: Boolean; + LastError: String; +begin + AttemptsLeft := 5; + Success := False; + while AttemptsLeft > 0 do begin + Dec(AttemptsLeft); + try + AsText := AValue; + Success := True; + Break; + except + // We could also just catch EClipboardException + on E:Exception do begin + LastError := E.Message; + Sleep(100); + end; + end; + end; + if not Success then + MainForm.LogSQL(LastError, lcError); +end;} + + +{procedure TWinControlHelper.TrySetFocus; +begin + try + if Enabled + and CanFocus then + SetFocus; + except + on E:EInvalidOperation do + MessageBeep(MB_ICONWARNING); + end; +end;} + + +{ TAppSettings } + +{constructor TAppSettings.Create; +var + rx: TRegExpr; + i: Integer; + DefaultSnippetsDirectory: String; + PortableLockFile: String; + NewFileHandle: THandle; +begin + inherited; + FRegistry := TRegistry.Create; + FReads := 0; + FWrites := 0; + + PortableLockFile := ExtractFilePath(ParamStr(0)) + FPortableLockFileBase; + + // Use filename from command line. If not given, use file in directory of executable. + rx := TRegExpr.Create; + rx.Expression := '^\-\-?psettings\=(.+)$'; + for i:=1 to ParamCount do begin + if rx.Exec(ParamStr(i)) then begin + FSettingsFile := rx.Match[1]; + break; + end; + end; + // Default settings file, if not given per command line + if FSettingsFile = '' then + FSettingsFile := ExtractFilePath(ParamStr(0)) + 'portable_settings.txt'; + // Backwards compatibility: only settings file exists, create lock file in that case + if FileExists(FSettingsFile) and (not FileExists(PortableLockFile)) then begin + NewFileHandle := FileCreate(PortableLockFile); + FileClose(NewFileHandle); + end; + + // Switch to portable mode if lock file exists. File content is ignored. + FPortableMode := FileExists(PortableLockFile); + FPortableModeReadOnly := False; + + if FPortableMode then begin + // Create file if only the lock file exists + if not FileExists(FSettingsFile) then begin + NewFileHandle := FileCreate(FSettingsFile); + FileClose(NewFileHandle); + end; + FBasePath := '\Software\' + APPNAME + ' Portable '+IntToStr(GetCurrentProcessId)+'\'; + try + ImportSettings(FSettingsFile); + except + on E:Exception do + MessageDlg(E.Message, mtError, [mbOK], 0, mbOK); + end; + end else begin + FBasePath := '\Software\' + APPNAME + '\'; + FSettingsFile := ''; + end; + + PrepareRegistry; + + InitSetting(asHiddenColumns, 'HiddenColumns', 0, False, '', True); + InitSetting(asFilter, 'Filter', 0, False, '', True); + InitSetting(asSort, 'Sort', 0, False, '', True); + InitSetting(asDisplayedColumnsSorted, 'DisplayedColumnsSorted', 0, False); + InitSetting(asLastSessions, 'LastSessions', 0, False, ''); + InitSetting(asLastActiveSession, 'LastActiveSession', 0, False, ''); + InitSetting(asAutoReconnect, 'AutoReconnect', 0, False); + InitSetting(asRestoreLastUsedDB, 'RestoreLastUsedDB', 0, True); + InitSetting(asLastUsedDB, 'lastUsedDB', 0, False, '', True); + InitSetting(asTreeBackground, 'TreeBackground', clNone, False, '', True); + InitSetting(asIgnoreDatabasePattern, 'IgnoreDatabasePattern', 0, False, '', True); + InitSetting(asLogFileDdl, 'LogFileDdl', 0, False, '', True); + InitSetting(asLogFileDml, 'LogFileDml', 0, False, '', True); + InitSetting(asLogFilePath, 'LogFilePath', 0, False, DirnameUserAppData + 'Logs\%session\%db\%y%m%d.sql', True); + if Screen.Fonts.IndexOf('Consolas') > -1 then + InitSetting(asFontName, 'FontName', 0, False, 'Consolas') + else + InitSetting(asFontName, 'FontName', 0, False, 'Courier New'); + InitSetting(asFontSize, 'FontSize', 9); + InitSetting(asTabWidth, 'TabWidth', 3); + InitSetting(asDataFontName, 'DataFontName', 0, False, 'Tahoma'); + InitSetting(asDataFontSize, 'DataFontSize', 8); + InitSetting(asDataLocalNumberFormat, 'DataLocalNumberFormat', 0, True); + InitSetting(asLowercaseHex, 'LowercaseHex', 0, True); + InitSetting(asHintsOnResultTabs, 'HintsOnResultTabs', 0, True); + InitSetting(asShowRowId, 'ShowRowId', 0, True); + InitSetting(asHightlightSameTextBackground, 'HightlightSameTextBackground', GetThemeColor(clInfoBk)); + InitSetting(asLogsqlnum, 'logsqlnum', 300); + InitSetting(asLogsqlwidth, 'logsqlwidth', 2000); + InitSetting(asSessionLogsDirectory, 'SessionLogsDirectory', 0, False, DirnameUserAppData + 'Sessionlogs\'); + InitSetting(asLogHorizontalScrollbar, 'LogHorizontalScrollbar', 0, False); + InitSetting(asSQLColActiveLine, 'SQLColActiveLine', 0, False, 'clNone'); + InitSetting(asSQLColMatchingBraceForeground, 'SQLColMatchingBraceForeground', 0, False, 'clBlack'); + InitSetting(asSQLColMatchingBraceBackground, 'SQLColMatchingBraceBackground', 0, False, 'clAqua'); + InitSetting(asMaxColWidth, 'MaxColWidth', 300); + InitSetting(asDatagridMaximumRows, 'DatagridMaximumRows', 100000); + InitSetting(asDatagridRowsPerStep, 'DatagridRowsPerStep', 1000); + InitSetting(asGridRowLineCount, 'GridRowLineCount', 1); + InitSetting(asColumnHeaderClick, 'ColumnHeaderClick', 0, True); + InitSetting(asReuseEditorConfiguration, 'ReuseEditorConfiguration', 0, True); + InitSetting(asLogToFile, 'LogToFile', 0, False); + InitSetting(asMainWinMaximized, 'MainWinMaximized', 0, False); + InitSetting(asMainWinLeft, 'MainWinLeft', 100); + InitSetting(asMainWinTop, 'MainWinTop', 100); + InitSetting(asMainWinWidth, 'MainWinWidth', 950); + InitSetting(asMainWinHeight, 'MainWinHeight', 600); + InitSetting(asMainWinOnMonitor, 'MainWinOnMonitor', 1); + InitSetting(asCoolBandIndex, 'CoolBand%sIndex', 0); + InitSetting(asCoolBandBreak, 'CoolBand%sBreak', 0, True); + InitSetting(asCoolBandWidth, 'CoolBand%sWidth', 0); + InitSetting(asToolbarShowCaptions, 'ToolbarShowCaptions', 0, False); + InitSetting(asQuerymemoheight, 'querymemoheight', 100); + InitSetting(asDbtreewidth, 'dbtreewidth', 270); + InitSetting(asDataPreviewHeight, 'DataPreviewHeight', 100); + InitSetting(asDataPreviewEnabled, 'DataPreviewEnabled', 0, False); + InitSetting(asLogHeight, 'sqloutheight', 80); + InitSetting(asQueryhelperswidth, 'queryhelperswidth', 200); + InitSetting(asStopOnErrorsInBatchMode, 'StopOnErrorsInBatchMode', 0, True); + InitSetting(asWrapLongLines, 'WrapLongLines', 0, False); + InitSetting(asCodeFolding, 'CodeFolding', 0, True); + InitSetting(asDisplayBLOBsAsText, 'DisplayBLOBsAsText', 0, True); + InitSetting(asSingleQueries, 'SingleQueries', 0, True); + InitSetting(asMemoEditorWidth, 'MemoEditorWidth', 500); + InitSetting(asMemoEditorHeight, 'MemoEditorHeight', 200); + InitSetting(asMemoEditorMaximized, 'MemoEditorMaximized', 0, False); + InitSetting(asMemoEditorWrap, 'MemoEditorWrap', 0, False); + InitSetting(asMemoEditorHighlighter, 'MemoEditorHighlighter_%s', 0, False, 'General', True); + InitSetting(asMemoEditorAlwaysFormatCode, 'MemoEditorAlwaysFormatCode', 0, False); + InitSetting(asDelimiter, 'Delimiter', 0, False, ';'); + InitSetting(asSQLHelpWindowLeft, 'SQLHelp_WindowLeft', 0); + InitSetting(asSQLHelpWindowTop, 'SQLHelp_WindowTop', 0); + InitSetting(asSQLHelpWindowWidth, 'SQLHelp_WindowWidth', 600); + InitSetting(asSQLHelpWindowHeight, 'SQLHelp_WindowHeight', 400); + InitSetting(asSQLHelpPnlLeftWidth, 'SQLHelp_PnlLeftWidth', 150); + InitSetting(asSQLHelpPnlRightTopHeight, 'SQLHelp_PnlRightTopHeight', 150); + InitSetting(asHost, 'Host', 0, False, '', True); + InitSetting(asUser, 'User', 0, False, '', True); + InitSetting(asPassword, 'Password', 0, False, '', True); + InitSetting(asCleartextPluginEnabled, 'CleartextPluginEnabled', 0, False, '', True); + InitSetting(asWindowsAuth, 'WindowsAuth', 0, False, '', True); + InitSetting(asLoginPrompt, 'LoginPrompt', 0, False, '', True); + InitSetting(asPort, 'Port', 0, False, '', True); + InitSetting(asLibrary, 'Library', 0, False, '', True); // Gets its default in TConnectionParameters.Create + InitSetting(asAllProviders, 'AllProviders', 0, False); + InitSetting(asSSHtunnelActive, 'SSHtunnelActive', -1, False, '', True); + InitSetting(asPlinkExecutable, 'PlinkExecutable', 0, False, 'plink.exe'); // Legacy support with global setting + InitSetting(asSshExecutable, 'SshExecutable', 0, False, '', True); + InitSetting(asSSHtunnelHost, 'SSHtunnelHost', 0, False, '', True); + InitSetting(asSSHtunnelHostPort, 'SSHtunnelHostPort', 22, False, '', True); + InitSetting(asSSHtunnelPort, 'SSHtunnelPort', 0, False, '', True); + InitSetting(asSSHtunnelUser, 'SSHtunnelUser', 0, False, '', True); + InitSetting(asSSHtunnelPassword, 'SSHtunnelPassword', 0, False, '', True); + InitSetting(asSSHtunnelTimeout, 'SSHtunnelTimeout', 4, False, '', True); + InitSetting(asSSHtunnelPrivateKey, 'SSHtunnelPrivateKey', 0, False, '', True); + InitSetting(asSSLActive, 'SSL_Active', 0, False, '', True); + InitSetting(asSSLKey, 'SSL_Key', 0, False, '', True); + InitSetting(asSSLCert, 'SSL_Cert', 0, False, '', True); + InitSetting(asSSLCA, 'SSL_CA', 0, False, '', True); + InitSetting(asSSLCipher, 'SSL_Cipher', 0, False, '', True); + InitSetting(asSSLVerification, 'SSL_Verification', 2, False, '', True); + InitSetting(asSSLWarnUnused, 'SSL_WarnUnused', 0, True); + InitSetting(asNetType, 'NetType', Integer(ntMySQL_TCPIP), False, '', True); + InitSetting(asCompressed, 'Compressed', 0, False, '', True); + InitSetting(asLocalTimeZone, 'LocalTimeZone', 0, False, '', True); + InitSetting(asQueryTimeout, 'QueryTimeout', 30, False, '', True); + InitSetting(asKeepAlive, 'KeepAlive', 20, False, '', True); + InitSetting(asStartupScriptFilename, 'StartupScriptFilename', 0, False, '', True); + InitSetting(asDatabases, 'Databases', 0, False, '', True); + InitSetting(asComment, 'Comment', 0, False, '', True); + InitSetting(asDatabaseFilter, 'DatabaseFilter', 0, False, ''); + InitSetting(asTableFilter, 'TableFilter', 0, False, ''); + InitSetting(asFilterVT, 'FilterVTHistory', 0, False, ''); + InitSetting(asExportSQLCreateDatabases, 'ExportSQL_CreateDatabases', 0, False); + InitSetting(asExportSQLCreateTables, 'ExportSQL_CreateTables', 0, False); + InitSetting(asExportSQLDataHow, 'ExportSQL_DataHow', 0); + InitSetting(asExportSQLDataInsertSize, 'ExportSQL_DataInsertSize', 1024); + InitSetting(asExportSQLFilenames, 'ExportSQL_Filenames', 0, False, ''); + InitSetting(asExportZIPFilenames, 'ExportSQL_ZipFilenames', 0, False, ''); + InitSetting(asExportSQLDirectories, 'ExportSQL_Directories', 0, False, ''); + InitSetting(asExportSQLDatabase, 'ExportSQL_Database', 0, False, ''); + InitSetting(asExportSQLServerDatabase, 'ExportSQL_ServerDatabase', 0, False, ''); + InitSetting(asExportSQLOutput, 'ExportSQL_Output', 0); + InitSetting(asExportSQLAddComments, 'ExportSQLAddComments', 0, True); + InitSetting(asExportSQLRemoveAutoIncrement, 'ExportSQLRemoveAutoIncrement', 0, False); + InitSetting(asExportSQLRemoveDefiner, 'ExportSQLRemoveDefiner', 0, True); + InitSetting(asGridExportWindowWidth, 'GridExportWindowWidth', 400); + InitSetting(asGridExportWindowHeight, 'GridExportWindowHeight', 480); + InitSetting(asGridExportOutputCopy, 'GridExportOutputCopy', 0, True); + InitSetting(asGridExportOutputFile, 'GridExportOutputFile', 0, False); + InitSetting(asGridExportFilename, 'GridExportFilename', 0, False, ''); + InitSetting(asGridExportRecentFiles, 'GridExportRecentFiles', 0, False, ''); + InitSetting(asGridExportEncoding, 'GridExportEncoding', 4); + InitSetting(asGridExportFormat, 'GridExportFormat', 0); + InitSetting(asGridExportSelection, 'GridExportSelection', 1); + InitSetting(asGridExportColumnNames, 'GridExportColumnNames', 0, True); + InitSetting(asGridExportIncludeAutoInc, 'GridExportAutoInc', 0, True); + InitSetting(asGridExportIncludeQuery, 'GridExportIncludeQuery', 0, False); + InitSetting(asGridExportRemoveLinebreaks, 'GridExportRemoveLinebreaks', 0, False); + InitSetting(asGridExportSeparator, 'GridExportSeparator', 0, False, ';'); + InitSetting(asGridExportEncloser, 'GridExportEncloser', 0, False, ''); + InitSetting(asGridExportTerminator, 'GridExportTerminator', 0, False, '\r\n'); + InitSetting(asGridExportNull, 'GridExportNull', 0, False, '\N'); + // Copy to clipboard defaults: + InitSetting(asGridExportClpColumnNames, 'GridExportClpColumnNames', 0, True); + InitSetting(asGridExportClpIncludeAutoInc, 'GridExportClpAutoInc', 0, True); + InitSetting(asGridExportClpRemoveLinebreaks, 'GridExportClpRemoveLinebreaks', 0, False); + InitSetting(asGridExportClpSeparator, 'GridExportClpSeparator', 0, False, ';'); + InitSetting(asGridExportClpEncloser, 'GridExportClpEncloser', 0, False, ''); + InitSetting(asGridExportClpTerminator, 'GridExportClpTerminator', 0, False, '\r\n'); + InitSetting(asGridExportClpNull, 'GridExportClpNull', 0, False, '\N'); + + InitSetting(asCSVImportSeparator, 'CSVSeparatorV2', 0, False, ';'); + InitSetting(asCSVImportEncloser, 'CSVEncloserV2', 0, False, '"'); + InitSetting(asCSVImportTerminator, 'CSVTerminator', 0, False, '\r\n'); + InitSetting(asCSVImportFieldEscaper, 'CSVImportFieldEscaperV2', 0, False, '"'); + InitSetting(asCSVImportWindowWidth, 'CSVImportWindowWidth', 530); + InitSetting(asCSVImportWindowHeight, 'CSVImportWindowHeight', 550); + InitSetting(asCSVImportFilename, 'loadfilename', 0, False, ''); + InitSetting(asCSVImportFieldsEnclosedOptionally, 'CSVImportFieldsEnclosedOptionallyV2', 0, True); + InitSetting(asCSVImportIgnoreLines, 'CSVImportIgnoreLines', 1); + InitSetting(asCSVImportLowPriority, 'CSVImportLowPriority', 0, True); + InitSetting(asCSVImportLocalNumbers, 'CSVImportLocalNumbers', 0, False); + InitSetting(asCSVImportDuplicateHandling, 'CSVImportDuplicateHandling', 2); + InitSetting(asCSVImportParseMethod, 'CSVImportParseMethod', 0); + InitSetting(asCSVKeepDialogOpen, 'CSVKeepDialogOpen', 0, False); + InitSetting(asUpdatecheck, 'Updatecheck', 0, False); + InitSetting(asUpdatecheckBuilds, 'UpdatecheckBuilds', 0, False); + InitSetting(asUpdatecheckInterval, 'UpdatecheckInterval', 3); + InitSetting(asUpdatecheckLastrun, 'UpdatecheckLastrun', 0, False, DateToStr(DateTimeNever)); + InitSetting(asUpdateCheckWindowWidth, 'UpdateCheckWindowWidth', 400); + InitSetting(asUpdateCheckWindowHeight, 'UpdateCheckWindowHeight', 460); + InitSetting(asTableToolsWindowWidth, 'TableTools_WindowWidth', 800); + InitSetting(asTableToolsWindowHeight, 'TableTools_WindowHeight', 420); + InitSetting(asTableToolsTreeWidth, 'TableTools_TreeWidth', 300); + InitSetting(asTableToolsFindTextTab, 'TableToolsFindTextTab', 0); + InitSetting(asTableToolsFindText, 'TableTools_FindText', 0, False, ''); + InitSetting(asTableToolsFindSQL, 'TableToolsFindSQL', 0, False, ''); + InitSetting(asTableToolsDatatype, 'TableTools_Datatype', 0); + InitSetting(asTableToolsFindCaseSensitive, 'TableTools_FindCaseSensitive', 0, False); + InitSetting(asTableToolsFindMatchType, 'TableToolsFindMatchType', 0); + InitSetting(asFileImportWindowWidth, 'FileImport_WindowWidth', 530); + InitSetting(asFileImportWindowHeight, 'FileImport_WindowHeight', 530); + InitSetting(asEditVarWindowWidth, 'EditVar_WindowWidth', 300); + InitSetting(asEditVarWindowHeight, 'EditVar_WindowHeight', 260); + InitSetting(asUsermanagerWindowWidth, 'Usermanager_WindowWidth', 500); + InitSetting(asUsermanagerWindowHeight, 'Usermanager_WindowHeight', 400); + InitSetting(asUsermanagerListWidth, 'Usermanager_ListWidth', 180); + InitSetting(asSelectDBOWindowWidth, 'SelectDBO_WindowWidth', 250); + InitSetting(asSelectDBOWindowHeight, 'SelectDBO_WindowHeight', 350); + InitSetting(asSessionManagerListWidth, 'SessionManager_ListWidth', 220); + InitSetting(asSessionManagerWindowWidth, 'SessionManager_WindowWidth', 700); + InitSetting(asSessionManagerWindowHeight, 'SessionManager_WindowHeight', 490); + InitSetting(asSessionManagerWindowLeft, 'SessionManager_WindowLeft', 50); + InitSetting(asSessionManagerWindowTop, 'SessionManager_WindowTop', 50); + InitSetting(asCopyTableWindowHeight, 'CopyTable_WindowHeight', 340); + InitSetting(asCopyTableWindowWidth, 'CopyTable_WindowWidth', 380); + InitSetting(asCopyTableColumns, 'CopyTable_Columns', 0, True); + InitSetting(asCopyTableKeys, 'CopyTable_Keys', 0, True); + InitSetting(asCopyTableForeignKeys, 'CopyTable_ForeignKeys', 0, True); + InitSetting(asCopyTableData, 'CopyTable_Data', 0, True); + InitSetting(asCopyTableRecentFilter, 'CopyTable_RecentFilter_%s', 0, False, ''); + InitSetting(asServerVersion, 'ServerVersion', 0, False, '', True); + InitSetting(asServerVersionFull, 'ServerVersionFull', 0, False, '', True); + InitSetting(asLastConnect, 'LastConnect', 0, False, DateToStr(DateTimeNever), True); + InitSetting(asConnectCount, 'ConnectCount', 0, False, '', True); + InitSetting(asRefusedCount, 'RefusedCount', 0, False, '', True); + InitSetting(asSessionCreated, 'SessionCreated', 0, False, '', True); + InitSetting(asDoUsageStatistics, 'DoUsageStatistics', 0, False); + InitSetting(asLastUsageStatisticCall, 'LastUsageStatisticCall', 0, False, DateToStr(DateTimeNever)); + InitSetting(asWheelZoom, 'WheelZoom', 0, True); + InitSetting(asDisplayBars, 'DisplayBars', 0, true); + InitSetting(asMySQLBinaries, 'MySQL_Binaries', 0, False, ''); + InitSetting(asSequalSuggestWindowWidth, 'SequalSuggestWindowWidth', 500); + InitSetting(asSequalSuggestWindowHeight, 'SequalSuggestWindowHeight', 400); + InitSetting(asSequalSuggestPrompt, 'SequalSuggestPrompt', 0, False, ''); + InitSetting(asSequalSuggestRecentPrompts, 'SequalSuggestRecentPrompts', 0, False, ''); + InitSetting(asReformatter, 'Reformatter', 0); + InitSetting(asReformatterNoDialog, 'ReformatterNoDialog', 0); + InitSetting(asAlwaysGenerateFilter, 'AlwaysGenerateFilter', 0, False); + InitSetting(asGenerateDataNumRows, 'GenerateDataNumRows', 1000); + InitSetting(asGenerateDataNullAmount, 'GenerateDataNullAmount', 10); + + // Default folder for snippets + if FPortableMode then + DefaultSnippetsDirectory := ExtractFilePath(ParamStr(0)) + else + DefaultSnippetsDirectory := DirnameUserDocuments; + DefaultSnippetsDirectory := DefaultSnippetsDirectory + 'Snippets\'; + InitSetting(asCustomSnippetsDirectory, 'CustomSnippetsDirectory', 0, False, DefaultSnippetsDirectory); + InitSetting(asPromptSaveFileOnTabClose, 'PromptSaveFileOnTabClose', 0, True); + // Restore tabs feature crashes often on old XP systems, see https://www.heidisql.com/forum.php?t=34044 + InitSetting(asRestoreTabs, 'RestoreTabs', 0, Win32MajorVersion >= 6); + InitSetting(asTabCloseOnDoubleClick, 'TabCloseOnDoubleClick', 0, True); + InitSetting(asTabCloseOnMiddleClick, 'TabCloseOnMiddleClick', 0, True); + InitSetting(asTabsInMultipleLines, 'TabsInMultipleLines', 0, True); + InitSetting(asTabIconsGrayscaleMode, 'TabIconsGrayscaleMode', 1); + InitSetting(asWarnUnsafeUpdates, 'WarnUnsafeUpdates', 0, True); + InitSetting(asQueryGridLongSortRowNum, 'QueryGridLongSortRowNum', 10000); + InitSetting(asCompletionProposal, 'CompletionProposal', 0, True); + InitSetting(asCompletionProposalInterval, 'CompletionProposalInterval', 500); + InitSetting(asCompletionProposalSearchOnMid, 'CompletionProposalSearchOnMid', 0, True); + InitSetting(asCompletionProposalWidth, 'CompletionProposalWidth', 350); + InitSetting(asCompletionProposalNbLinesInWindow,'CompletionProposalNbLinesInWindow', 12); + InitSetting(asAutoUppercase, 'AutoUppercase', 0, True); + InitSetting(asTabsToSpaces, 'TabsToSpaces', 0, False); + InitSetting(asFilterPanel, 'FilterPanel', 0, True); + InitSetting(asAllowMultipleInstances, 'AllowMultipleInstances', 0, True); + InitSetting(asFindDialogSearchHistory, 'FindDialogSearchHistory', 0, False, ''); + InitSetting(asFindDialogReplaceHistory, 'FindDialogReplaceHistory', 0, False, ''); + InitSetting(asGUIFontName, 'GUIFontName', 0, False, ''); + InitSetting(asGUIFontSize, 'GUIFontSize', 8); + InitSetting(asTheme, 'Theme', 0, False, 'Windows'); + InitSetting(asIconPack, 'IconPack', 0, False, 'Icons8'); + InitSetting(asWebSearchBaseUrl, 'WebSearchBaseUrl', 0, False, 'https://www.ecosia.org/search?q=%query'); + InitSetting(asMaxQueryResults, 'MaxQueryResults', 10); + InitSetting(asLogErrors, 'LogErrors', 0, True); + InitSetting(asLogUserSQL, 'LogUserSQL', 0, True); + InitSetting(asLogSQL, 'LogSQL', 0, True); + InitSetting(asLogScript, 'LogScript', 0, False); + InitSetting(asLogInfos, 'LogInfos', 0, True); + InitSetting(asLogDebug, 'LogDebug', 0, False); + InitSetting(asLogTimestamp, 'LogTimestamp', 0, False); + InitSetting(asFieldColorNumeric, 'FieldColor_Numeric', $00FF0000); + InitSetting(asFieldColorReal, 'FieldColor_Real', $00FF0048); + InitSetting(asFieldColorText, 'FieldColor_Text', $00008000); + InitSetting(asFieldColorBinary, 'FieldColor_Binary', $00800080); + InitSetting(asFieldColorDatetime, 'FieldColor_Datetime', $00000080); + InitSetting(asFieldColorSpatial, 'FieldColor_Spatial', $00808000); + InitSetting(asFieldColorOther, 'FieldColor_Other', $00008080); + InitSetting(asFieldEditorBinary, 'FieldEditor_Binary', 0, True); + InitSetting(asFieldEditorDatetime, 'FieldEditor_Datetime', 0, True); + InitSetting(asFieldEditorDatetimePrefill, 'FieldEditor_Datetime_Prefill', 0, True); + InitSetting(asFieldEditorEnum, 'FieldEditor_Enum', 0, True); + InitSetting(asFieldEditorSet, 'FieldEditor_Set', 0, True); + InitSetting(asFieldNullBackground, 'Field_NullBackground', clNone); + InitSetting(asRowBackgroundEven, 'RowBackgroundEven', clNone); + InitSetting(asRowBackgroundOdd, 'RowBackgroundOdd', clNone); + InitSetting(asGroupTreeObjects, 'GroupTreeObjects', 0, False); + InitSetting(asDisplayObjectSizeColumn, 'DisplayObjectSizeColumn', 0, True); + InitSetting(asActionShortcut1, 'Shortcut1_%s', 0); + InitSetting(asActionShortcut2, 'Shortcut2_%s', 0); + InitSetting(asHighlighterForeground, 'SQL Attr %s Foreground', 0); + InitSetting(asHighlighterBackground, 'SQL Attr %s Background', 0); + InitSetting(asHighlighterStyle, 'SQL Attr %s Style', 0); + InitSetting(asSQLfile, 'SQLFile%s', 0, False, ''); + InitSetting(asListColWidths, 'ColWidths_%s', 0, False, ''); + InitSetting(asListColsVisible, 'ColsVisible_%s', 0, False, ''); + InitSetting(asListColPositions, 'ColPositions_%s', 0, False, ''); + InitSetting(asListColSort, 'ColSort_%s', 0, False, ''); + InitSetting(asSessionFolder, 'Folder', 0, False, '', True); + InitSetting(asRecentFilter, '%s', 0, False, '', True); + InitSetting(asTimestampColumns, 'TimestampColumns', 0, False, '', True); + InitSetting(asDateTimeEditorCursorPos, 'DateTimeEditor_CursorPos_Type%s', 0); + InitSetting(asAppLanguage, 'Language', 0, False, ''); + InitSetting(asAutoExpand, 'AutoExpand', 0, False); + InitSetting(asDoubleClickInsertsNodeText, 'DoubleClickInsertsNodeText', 0, True); + InitSetting(asForeignDropDown, 'ForeignDropDown', 0, True); + InitSetting(asIncrementalSearch, 'IncrementalSearch', 0, True); + InitSetting(asQueryHistoryEnabled, 'QueryHistory', 0, True); + InitSetting(asQueryHistoryKeepDays, 'QueryHistoryKeeypDays', 30); + InitSetting(asColumnSelectorWidth, 'ColumnSelectorWidth', 200, False, ''); + InitSetting(asColumnSelectorHeight, 'ColumnSelectorHeight', 270, False, ''); + InitSetting(asDonatedEmail, 'DonatedEmail', 0, False, ''); + InitSetting(asFavoriteObjects, 'FavoriteObjects', 0, False, '', True); + InitSetting(asFavoriteObjectsOnly, 'FavoriteObjectsOnly', 0, False); // No longer used + InitSetting(asFullTableStatus, 'FullTableStatus', 0, True, '', True); + InitSetting(asLineBreakStyle, 'LineBreakStyle', Integer(lbsWindows)); + InitSetting(asPreferencesWindowWidth, 'PreferencesWindowWidth', 740); + InitSetting(asPreferencesWindowHeight, 'PreferencesWindowHeight', 500); + InitSetting(asFileDialogEncoding, 'FileDialogEncoding_%s', 0); + InitSetting(asThemePreviewWidth, 'ThemePreviewWidth', 300); + InitSetting(asThemePreviewHeight, 'ThemePreviewHeight', 200); + InitSetting(asThemePreviewTop, 'ThemePreviewTop', 300); + InitSetting(asThemePreviewLeft, 'ThemePreviewLeft', 300); + InitSetting(asCreateDbCollation, 'CreateDbCollation', 0, False, ''); + InitSetting(asRealTrailingZeros, 'RealTrailingZeros', 1); + InitSetting(asWebOnceAction, 'WebOnceAction', 0, False, DateToStr(DateTimeNever)); + + // Initialization values + FRestoreTabsInitValue := ReadBool(asRestoreTabs); + +end;} + + +{destructor TAppSettings.Destroy; +var + AllKeys: TStringList; + i: Integer; + Proc: TProcessEntry32; + ProcRuns: Boolean; + SnapShot: THandle; + rx: TRegExpr; +begin + // Export settings into textfile in portable mode. + if FPortableMode then try + try + ExportSettings; + except + // do nothing, even ShowMessage or ErrorDialog would trigger timer events followed by crashes; + end; + FRegistry.CloseKey; + FRegistry.DeleteKey(FBasePath); + + // Remove dead keys from instances which didn't close clean, e.g. because of an AV + SnapShot := CreateToolhelp32Snapshot(TH32CS_SnapProcess, 0); + Proc.dwSize := Sizeof(Proc); + FRegistry.OpenKeyReadOnly('\Software\'); + AllKeys := TStringList.Create; + FRegistry.GetKeyNames(AllKeys); + rx := TRegExpr.Create; + rx.Expression := '^' + QuoteRegExprMetaChars(APPNAME) + ' Portable (\d+)$'; + for i:=0 to AllKeys.Count-1 do begin + if not rx.Exec(AllKeys[i]) then + Continue; + ProcRuns := False; + if Process32First(SnapShot, Proc) then while True do begin + ProcRuns := rx.Match[1] = IntToStr(Proc.th32ProcessID); + if ProcRuns or (not Process32Next(SnapShot, Proc)) then + break; + end; + if not ProcRuns then + FRegistry.DeleteKey(AllKeys[i]); + end; + FRegistry.CloseKey; + CloseHandle(SnapShot); + AllKeys.Free; + rx.Free; + except + on E:Exception do // Prefer ShowMessage, see http://www.heidisql.com/forum.php?t=14001 + ShowMessage('Error: '+E.Message); + end; + FRegistry.Free; + inherited; +end;} + + +{procedure TAppSettings.InitSetting(Index: TAppSettingIndex; Name: String; + DefaultInt: Integer=0; DefaultBool: Boolean=False; DefaultString: String=''; + Session: Boolean=False); +begin + FSettings[Index].Name := Name; + FSettings[Index].Session := Session; + FSettings[Index].DefaultInt := DefaultInt; + FSettings[Index].DefaultBool := DefaultBool; + FSettings[Index].DefaultString := DefaultString; + FSettings[Index].Synced := False; +end;} + + +{procedure TAppSettings.SetSessionPath(Value: String); +begin + // Following calls may want to read or write some session specific setting + if Value <> FSessionPath then begin + FSessionPath := Value; + PrepareRegistry; + end; +end;} + + +{procedure TAppSettings.ResetPath; +begin + SessionPath := ''; +end;} + + +{procedure TAppSettings.StorePath; +begin + FStoredPath := SessionPath; +end;} + +{procedure TAppSettings.RestorePath; +begin + SessionPath := FStoredPath; +end;} + + +{procedure TAppSettings.PrepareRegistry; +var + Folder: String; +begin + // Open the wanted registry path + Folder := FBasePath; + if FSessionPath <> '' then + Folder := Folder + REGKEY_SESSIONS + '\' + FSessionPath; + if '\'+FRegistry.CurrentPath <> Folder then try + FRegistry.OpenKey(Folder, True); + except + on E:Exception do begin + // Recreate exception with a more useful message + E.Message := E.Message + CRLF + CRLF + 'While trying to open registry key "'+Folder+'"'; + raise; + end; + end; +end;} + + +{function TAppSettings.GetValueNames: TStringList; +begin + PrepareRegistry; + Result := TStringList.Create; + FRegistry.GetValueNames(Result); +end;} + + +{function TAppSettings.GetValueName(Index: TAppSettingIndex): String; +begin + Result := FSettings[Index].Name; +end;} + + +{function TAppSettings.GetKeyNames: TStringList; +begin + PrepareRegistry; + Result := TStringList.Create; + FRegistry.GetKeyNames(Result); +end;} + + +{function TAppSettings.DeleteValue(Index: TAppSettingIndex; FormatName: String=''): Boolean; +var + ValueName: String; +begin + PrepareRegistry; + ValueName := GetValueName(Index); + if FormatName <> '' then + ValueName := Format(ValueName, [FormatName]); + Result := FRegistry.DeleteValue(ValueName); + FSettings[Index].Synced := False; +end;} + + +{function TAppSettings.DeleteValue(ValueName: String): Boolean; +begin + Result := FRegistry.DeleteValue(ValueName); +end;} + + +{procedure TAppSettings.DeleteCurrentKey; +var + KeyPath: String; +begin + // Delete the current registry key + // Note that, contrary to the documentation, .DeleteKey is done even when this key has subkeys + PrepareRegistry; + if FSessionPath.IsEmpty then + raise Exception.CreateFmt(_('No path set, won''t delete root key %s'), [FRegistry.CurrentPath]) + else begin + KeyPath := REGKEY_SESSIONS + '\' + FSessionPath; + ResetPath; + FRegistry.DeleteKey(KeyPath); + end; +end;} + + +{procedure TAppSettings.MoveCurrentKey(TargetPath: String); +var + KeyPath: String; +begin + PrepareRegistry; + if FSessionPath.IsEmpty then + raise Exception.CreateFmt(_('No path set, won''t move root key %s'), [FRegistry.CurrentPath]) + else begin + KeyPath := REGKEY_SESSIONS + '\' + FSessionPath; + ResetPath; + FRegistry.MoveKey(KeyPath, TargetPath, True); + end; +end;} + + +{function TAppSettings.ValueExists(Index: TAppSettingIndex): Boolean; +var + ValueName: String; +begin + PrepareRegistry; + ValueName := GetValueName(Index); + Result := FRegistry.ValueExists(ValueName); +end;} + + +{function TAppSettings.SessionPathExists(SessionPath: String): Boolean; +begin + Result := FRegistry.KeyExists(FBasePath + REGKEY_SESSIONS + '\' + SessionPath); +end;} + + +{function TAppSettings.IsEmptyKey: Boolean; +var + TestList: TStringList; +begin + TestList := GetValueNames; + Result := (not FRegistry.HasSubKeys) and (TestList.Count = 0); + TestList.Free; +end;} + + +{function TAppSettings.GetDefaultInt(Index: TAppSettingIndex): Integer; +begin + // Return default integer value + Result := FSettings[Index].DefaultInt; +end;} + + +{function TAppSettings.GetDefaultBool(Index: TAppSettingIndex): Boolean; +begin + // Return default boolean value + Result := FSettings[Index].DefaultBool; +end;} + + +{function TAppSettings.GetDefaultString(Index: TAppSettingIndex): String; +begin + // Return default string value + Result := FSettings[Index].DefaultString; +end;} + + +{procedure TAppSettings.Read(Index: TAppSettingIndex; FormatName: String; + DataType: TAppSettingDataType; var I: Integer; var B: Boolean; var S: String; + DI: Integer; DB: Boolean; DS: String); +var + ValueName: String; +begin + // Read user setting value from registry + I := FSettings[Index].DefaultInt; + B := FSettings[Index].DefaultBool; + S := FSettings[Index].DefaultString; + if DI<>0 then I := DI; + if DB<>False then B := DB; + if DS<>'' then S := DS; + ValueName := FSettings[Index].Name; + if FormatName <> '' then + ValueName := Format(ValueName, [FormatName]); + if FSettings[Index].Session and FSessionPath.IsEmpty then + raise Exception.Create(_('Attempt to read session setting without session path')); + if (not FSettings[Index].Session) and (not FSessionPath.IsEmpty) then + SessionPath := '' + else + PrepareRegistry; + if FSettings[Index].Synced then begin + case DataType of + adInt: I := FSettings[Index].CurrentInt; + adBool: B := FSettings[Index].CurrentBool; + adString: S := FSettings[Index].CurrentString; + else raise Exception.CreateFmt(_(SUnsupportedSettingsDatatype), [FSettings[Index].Name]); + end; + end else if FRegistry.ValueExists(ValueName) then begin + Inc(FReads); + case DataType of + adInt: I := FRegistry.ReadInteger(ValueName); + adBool: B := FRegistry.ReadBool(ValueName); + adString: S := FRegistry.ReadString(ValueName); + else raise Exception.CreateFmt(_(SUnsupportedSettingsDatatype), [FSettings[Index].Name]); + end; + end; + if (FormatName = '') and (FSessionPath = '') then begin + FSettings[Index].Synced := True; + FSettings[Index].CurrentInt := I; + FSettings[Index].CurrentBool := B; + FSettings[Index].CurrentString := S; + end; +end;} + + +{function TAppSettings.ReadInt(Index: TAppSettingIndex; FormatName: String=''; Default: Integer=0): Integer; +var + S: String; + B: Boolean; +begin + Read(Index, FormatName, adInt, Result, B, S, Default, False, ''); +end;} + + +{function TAppSettings.ReadIntDpiAware(Index: TAppSettingIndex; AControl: TControl; FormatName: String=''; Default: Integer=0): Integer; +begin + Result := ReadInt(Index, FormatName, Default); + Result := Round(Result * AControl.ScaleFactor); +end;} + + +{function TAppSettings.ReadBool(Index: TAppSettingIndex; FormatName: String=''; Default: Boolean=False): Boolean; +var + I: Integer; + S: String; +begin + Read(Index, FormatName, adBool, I, Result, S, 0, Default, ''); +end;} + + +{function TAppSettings.ReadString(Index: TAppSettingIndex; FormatName: String=''; Default: String=''): String; +var + I: Integer; + B: Boolean; +begin + Read(Index, FormatName, adString, I, B, Result, 0, False, Default); +end;} + + +{function TAppSettings.ReadString(ValueName: String): String; +begin + PrepareRegistry; + Result := FRegistry.ReadString(ValueName); +end;} + + +{procedure TAppSettings.Write(Index: TAppSettingIndex; FormatName: String; + DataType: TAppSettingDataType; I: Integer; B: Boolean; S: String); +var + ValueName: String; + SameAsCurrent: Boolean; +begin + // Write user setting value to registry + ValueName := FSettings[Index].Name; + if FormatName <> '' then + ValueName := Format(ValueName, [FormatName]); + if FSettings[Index].Session and FSessionPath.IsEmpty then + raise Exception.Create(_('Attempt to write session setting without session path')); + if (not FSettings[Index].Session) and (not FSessionPath.IsEmpty) then + SessionPath := '' + else + PrepareRegistry; + case DataType of + adInt: begin + SameAsCurrent := FSettings[Index].Synced and (I = FSettings[Index].CurrentInt); + if not SameAsCurrent then begin + FRegistry.WriteInteger(ValueName, I); + Inc(FWrites); + end; + FSettings[Index].CurrentInt := I; + end; + adBool: begin + SameAsCurrent := FSettings[Index].Synced and (B = FSettings[Index].CurrentBool); + if not SameAsCurrent then begin + FRegistry.WriteBool(ValueName, B); + Inc(FWrites); + end; + FSettings[Index].CurrentBool := B; + end; + adString: begin + SameAsCurrent := FSettings[Index].Synced and (S = FSettings[Index].CurrentString); + if not SameAsCurrent then begin + FRegistry.WriteString(ValueName, S); + Inc(FWrites); + end; + FSettings[Index].CurrentString := S; + end; + else + raise Exception.CreateFmt(_(SUnsupportedSettingsDatatype), [FSettings[Index].Name]); + end; + if (FormatName = '') and (FSessionPath = '') then + FSettings[Index].Synced := True; +end;} + + +{procedure TAppSettings.WriteInt(Index: TAppSettingIndex; Value: Integer; FormatName: String=''); +begin + Write(Index, FormatName, adInt, Value, False, ''); +end;} + + +{procedure TAppSettings.WriteIntDpiAware(Index: TAppSettingIndex; AControl: TControl; Value: Integer; FormatName: String=''); +begin + Value := Round(Value / AControl.ScaleFactor); + WriteInt(Index, Value, FormatName); +end;} + + +{procedure TAppSettings.WriteBool(Index: TAppSettingIndex; Value: Boolean; FormatName: String=''); +begin + Write(Index, FormatName, adBool, 0, Value, ''); +end;} + + +{procedure TAppSettings.WriteString(Index: TAppSettingIndex; Value: String; FormatName: String=''); +begin + Write(Index, FormatName, adString, 0, False, Value); +end;} + + +{procedure TAppSettings.WriteString(ValueName, Value: String); +begin + PrepareRegistry; + FRegistry.WriteString(ValueName, Value); +end;} + + +{function TAppSettings.GetSessionNames(ParentPath: String; var Folders: TStringList): TStringList; +var + i: Integer; + CurPath: String; +begin + ResetPath; + CurPath := FBasePath + REGKEY_SESSIONS + '\' + ParentPath; + FRegistry.OpenKey(CurPath, False); + Result := TStringList.Create; + FRegistry.GetKeyNames(Result); + for i:=Result.Count-1 downto 0 do begin + // Issue #1111 describes a recursive endless loop, which may be caused by an empty key name here? + if Result[i].IsEmpty then + Continue; + // ... may also be caused by some non accessible key. Check result of .OpenKey before looking for "Folder" value: + if FRegistry.OpenKey(CurPath+'\'+Result[i], False) then begin + if FRegistry.ValueExists(GetValueName(asSessionFolder)) then begin + Folders.Add(Result[i]); + Result.Delete(i); + end; + end; + end; +end;} + + +{procedure TAppSettings.GetSessionPaths(ParentPath: String; var Sessions: TStringList); +var + Folders, Names: TStringList; + i: Integer; +begin + Folders := TStringList.Create; + Names := GetSessionNames(ParentPath, Folders); + for i:=0 to Names.Count-1 do + Sessions.Add(ParentPath+Names[i]); + for i:=0 to Folders.Count-1 do + GetSessionPaths(ParentPath+Folders[i]+'\', Sessions); + Sessions.Sort; + Names.Free; + Folders.Free; +end;} + + +{procedure TAppSettings.ImportSettings(Filename: String); +var + Content, Name, Value, KeyPath: String; + Lines, Segments: TStringList; + i: Integer; + DataType: TRegDataType; +begin + // Load registry settings from file + + if not FileExists(Filename) then begin + raise Exception.CreateFmt('File does not exist: %s', [Filename]); + end; + + Content := ReadTextfile(FileName, UTF8NoBOMEncoding); + Lines := Explode(CRLF, Content); + for i:=0 to Lines.Count-1 do begin + // Each line has 3 segments: reg path | data type | value. Continue if explode finds less or more than 3. + Segments := Explode(DELIMITER, Lines[i]); + if Segments.Count <> 3 then + continue; + KeyPath := FBasePath + ExtractFilePath(Segments[0]); + Name := ExtractFileName(Segments[0]); + DataType := TRegDataType(StrToInt(Segments[1])); + FRegistry.OpenKey(KeyPath, True); + if FRegistry.ValueExists(Name) then + Continue; // Don't touch value if already there + Value := ''; + if Segments.Count >= 3 then + Value := Segments[2]; + case DataType of + rdString: begin + Value := StringReplace(Value, CHR13REPLACEMENT, #13, [rfReplaceAll]); + Value := StringReplace(Value, CHR10REPLACEMENT, #10, [rfReplaceAll]); + FRegistry.WriteString(Name, Value); + end; + rdInteger: + FRegistry.WriteInteger(Name, MakeInt(Value)); + rdBinary, rdUnknown, rdExpandString: + ErrorDialog(Name+' has an unsupported data type.'); + end; + Segments.Free; + end; + Lines.Free; +end;} + + +{function TAppSettings.ExportSettings(Filename: String): Boolean; +var + Content, Value: String; + DataType: TRegDataType; + + procedure ReadKeyToContent(Path: String); + var + Names: TStringList; + i: Integer; + SubPath: String; + begin + // Recursively read values in keys and their subkeys into "content" variable + FRegistry.OpenKey(Path, True); + SubPath := Copy(Path, Length(FBasePath)+1, MaxInt); + Names := TStringList.Create; + FRegistry.GetValueNames(Names); + for i:=0 to Names.Count-1 do begin + DataType := FRegistry.GetDataType(Names[i]); + Content := Content + + SubPath + Names[i] + DELIMITER + + IntToStr(Integer(DataType)) + DELIMITER; + case DataType of + rdString: begin + Value := FRegistry.ReadString(Names[i]); + Value := StringReplace(Value, #13, CHR13REPLACEMENT, [rfReplaceAll]); + Value := StringReplace(Value, #10, CHR10REPLACEMENT, [rfReplaceAll]); + end; + rdInteger: + Value := IntToStr(FRegistry.ReadInteger(Names[i])); + rdBinary, rdUnknown, rdExpandString: + ErrorDialog(Names[i]+' has an unsupported data type.'); + end; + Content := Content + Value + CRLF; + end; + Names.Clear; + FRegistry.GetKeyNames(Names); + for i:=0 to Names.Count-1 do + ReadKeyToContent(Path + Names[i] + '\'); + Names.Free; + end; + +begin + // Save registry settings to file + Content := ''; + ReadKeyToContent(FBasePath); + SaveUnicodeFile(FileName, Content, UTF8NoBOMEncoding); + Result := True; +end;} + + +{function TAppSettings.ExportSettings: Boolean; +begin + Result := False; + if not FPortableModeReadOnly then begin + try + ExportSettings(FSettingsFile); + Result := True; + except + on E:Exception do begin + FPortableModeReadOnly := True; + Raise Exception.Create(E.ClassName + ': ' + E.Message + CRLF + CRLF + + f_('Switching to read-only mode. Settings won''t be saved. Use the command line parameter %s to use a custom file path.', ['--psettings']) + ); + end; + end; + end; +end;} + + +{function TAppSettings.DirnameUserAppData: String; +begin + // User folder for HeidiSQL's data (<user name>\Application Data) + Result := GetShellFolder(FOLDERID_RoamingAppData) + '\' + APPNAME + '\'; + if not DirectoryExists(Result) then begin + ForceDirectories(Result); + end; +end;} + + +{function TAppSettings.DirnameUserDocuments: String; +begin + // "HeidiSQL" folder under user's documents folder, e.g. c:\Users\Mike\Documents\HeidiSQL\ + Result := GetShellFolder(FOLDERID_Documents) + '\' + APPNAME + '\'; + // Do not auto-create it, as we only use it for snippets which can also have a custom path. +end;} + + +{function TAppSettings.DirnameSnippets: String; +begin + // Folder for snippets + Result := ReadString(asCustomSnippetsDirectory); + if Result.IsEmpty then + Result := GetDefaultString(asCustomSnippetsDirectory); + Result := IncludeTrailingBackslash(Result); + if not DirectoryExists(Result) then begin + ForceDirectories(Result); + end; +end;} + + +{function TAppSettings.DirnameBackups: String; +begin + // Create backup folder if it does not exist and return it + if PortableMode then begin + Result := ExtractFilePath(Application.ExeName) + 'Backups\' + end else begin + Result := DirnameUserAppData + 'Backups\'; + end; + if not DirectoryExists(Result) then begin + ForceDirectories(Result); + end; +end;} + + +{function TAppSettings.DirnameHighlighters: string; +begin + if PortableMode then begin + Result := ExtractFilePath(Application.ExeName) + 'Highlighters\' + end else begin + Result := DirnameUserAppData + 'Highlighters\'; + end; + if not DirectoryExists(Result) then begin + ForceDirectories(Result); + end; +end;} + + + +{ TUTF8NoBOMEncoding } + +{function TUTF8NoBOMEncoding.GetPreamble: TBytes; +begin + SetLength(Result, 0); +end;} + + +initialization + +NumberChars := ['0'..'9', FormatSettings.DecimalSeparator, FormatSettings.ThousandSeparator]; + +LibHandleUser32 := LoadLibrary('User32.dll'); + +//UTF8NoBOMEncoding := TUTF8NoBOMEncoding.Create; + +DateTimeNever := MinDateTime; + +//ConfirmIcon := TIcon.Create; +//ConfirmIcon.LoadFromResourceName(hInstance, 'Z_ICONQUESTION'); + +end. + + diff --git a/source/const.inc b/source/const.inc new file mode 100644 index 00000000..47cb3026 --- /dev/null +++ b/source/const.inc @@ -0,0 +1,106 @@ +// Common constants +const + + // Line breaks + // TODO: use sLineBreak instead + CRLF = #13#10; + LB_UNIX = #10; + LB_MAC = #13; + LB_WIDE = WideChar($2027); + + // Placeholder text for NULL values + TEXT_NULL = '(NULL)'; + + // General things + APPNAME = 'HeidiSQL'; + APPDOMAIN = 'https://www.heidisql.com/'; + REGKEY_SESSIONS = 'Servers'; + REGKEY_QUERYHISTORY = 'QueryHistory'; + REGKEY_RECENTFILTERS = 'RecentFilters'; + // Some unique char, used to separate e.g. selected columns in registry + DELIM = '|'; + CHR10REPLACEMENT = '<}}}>'; + CHR13REPLACEMENT = '<{{{>'; + LINEDELIMITER = '<|||>'; + + COLORSHIFT_NULLFIELDS = 70; // Brightness adjustment to add to normal field colors for NULL values + COLORSHIFT_SORTCOLUMNS = 12; // Brightness adjustment to add to sorted column backgrounds + + // Various iconindexes + ICONINDEX_PRIMARYKEY = 25; + ICONINDEX_FIELD = 42; + ICONINDEX_INDEXKEY = 23; + ICONINDEX_UNIQUEKEY = 24; + ICONINDEX_FULLTEXTKEY = 22; + ICONINDEX_SPATIALKEY = 126; + ICONINDEX_FOREIGNKEY = 136; + ICONINDEX_SERVER = 36; + ICONINDEX_DB = 5; + ICONINDEX_HIGHLIGHTMARKER = 157; + ICONINDEX_TABLE = 14; + ICONINDEX_VIEW = 81; + ICONINDEX_STOREDPROCEDURE = 119; + ICONINDEX_STOREDFUNCTION = 35; + ICONINDEX_TRIGGER = 137; + ICONINDEX_FUNCTION = 13; + ICONINDEX_EVENT = 80; + ICONINDEX_KEYWORD = 25; + + // Size of byte units + {Kibibyte} SIZE_KB = Int64(1024); + {Mebibyte} SIZE_MB = Int64(1048576); + {Gibibyte} SIZE_GB = Int64(1073741824); + {Tebibyte} SIZE_TB = Int64(1099511627776); + {Pebibyte} SIZE_PB = Int64(1125899906842624); + {Exbibyte} SIZE_EB = Int64(1152921504606846976); + + // Size of byte units for formatting purposes + {Kibibyte} FSIZE_KB = Int64(1000); + {Mebibyte} FSIZE_MB = Int64(1024000); + {Gibibyte} FSIZE_GB = Int64(1048576000); + {Tebibyte} FSIZE_TB = Int64(1073741824000); + {Pebibyte} FSIZE_PB = Int64(1099511627776000); + {Exbibyte} FSIZE_EB = Int64(1125899906842624000); + + // Abbreviations of byte unit names + {Bytes} NAME_BYTES = ' B'; + {Kibibyte} NAME_KB = ' KiB'; + {Mebibyte} NAME_MB = ' MiB'; + {Gibibyte} NAME_GB = ' GiB'; + {Tebibyte} NAME_TB = ' TiB'; + {Pebibyte} NAME_PB = ' PiB'; + {Exbibyte} NAME_EB = ' EiB'; + + // Data grid: How many bytes to fetch from data fields that are potentially large. + GRIDMAXDATA: Int64 = 256; + + BACKUP_MAXFILESIZE = 10 * SIZE_MB; + BACKUP_FILEPATTERN: String = 'query-tab-%s.sql'; + + VTREE_NOTLOADED = 0; + VTREE_NOTLOADED_PURGECACHE = 1; + VTREE_LOADED = 2; + + // Modification indicator for TControl.Tag + MODIFIEDFLAG = 10; + + SUnhandledNodeIndex = 'Unhandled tree node index'; + MSG_NOGRIDEDITING = 'Selected columns don''t contain a sufficient set of key columns to allow editing. Please select primary or unique key columns, or just all columns.'; + SIdle = 'Idle.'; + SUnsupported = 'Unsupported by this server'; + SUnsupportedSettingsDatatype = 'Unsupported datatype for setting "%s"'; + SNotImplemented = 'Method not implemented for this connection type'; + MsgSQLError: String = 'SQL Error (%d): %s'; + MsgSQLErrorMultiStatements: String = 'SQL Error (%d) in statement #%d: %s'; + MsgUnhandledNetType: String = 'Unhandled connection type (%d)'; + MsgUnhandledControl: String = 'Unhandled control in %s'; + MsgDisconnect: String = 'Connection to %s closed at %s'; + MsgInvalidColumn: String = 'Column #%d not available. Query returned %d columns and %d rows.'; + FILEFILTER_SQLITEDB = '*.sqlite3;*.sqlite;*.db;*.s3db'; + FILEEXT_SQLITEDB = 'sqlite3'; + PROPOSAL_ITEM_HEIGHT = 18; + // Note the following should be in sync to what MySQL returns from SHOW WARNINGS + SLogPrefixWarning = 'Warning'; + SLogPrefixNote = 'Note'; + SLogPrefixInfo = 'Info'; + diff --git a/source/dbconnection.pas b/source/dbconnection.pas new file mode 100644 index 00000000..a603f20e --- /dev/null +++ b/source/dbconnection.pas @@ -0,0 +1,37 @@ +unit dbconnection; + +{$mode delphi}{$H+} + +interface + +uses + Classes, SysUtils, Generics.Collections, Generics.Defaults; + +type + TDBConnection = class; + + { TDBConnection } + + TDBLogCategory = (lcInfo, lcSQL, lcUserFiredSQL, lcError, lcDebug, lcScript); + TDBLogItem = class(TObject) + public + Category: TDBLogCategory; + LineText: String; + Connection: TDBConnection; + end; + TDBLogItems = TObjectList<TDBLogItem>; + TDBLogEvent = procedure(Msg: String; Category: TDBLogCategory=lcInfo; Connection: TDBConnection=nil) of object; + TDBEvent = procedure(Connection: TDBConnection; Database: String) of object; + + TDBConnection = class(TComponent) + private + FActive: Boolean; + public + end; + TDBConnectionList = TObjectList<TDBConnection>; + + +implementation + +end. + diff --git a/source/dbstructures.mysql.pas b/source/dbstructures.mysql.pas new file mode 100644 index 00000000..31c38842 --- /dev/null +++ b/source/dbstructures.mysql.pas @@ -0,0 +1,3389 @@ +unit dbstructures.mysql; + +{$mode delphi}{$H+} + +interface + +uses + Classes, SysUtils, dbstructures; + + +const + // General declarations + MYSQL_ERRMSG_SIZE = 512; + SQLSTATE_LENGTH = 5; + SCRAMBLE_LENGTH = 20; + MYSQL_PORT = 3306; + LOCAL_HOST = 'localhost'; + NAME_LEN = 64; + PROTOCOL_VERSION = 10; + FRM_VER = 6; + + // Field's flags + NOT_NULL_FLAG = 1; + PRI_KEY_FLAG = 2; + UNIQUE_KEY_FLAG = 4; + MULTIPLE_KEY_FLAG = 8; + BLOB_FLAG = 16; + UNSIGNED_FLAG = 32; + ZEROFILL_FLAG = 64; + BINARY_FLAG = 128; + ENUM_FLAG = 256; + AUTO_INCREMENT_FLAG = 512; + TIMESTAMP_FLAG = 1024; + SET_FLAG = 2048; + NO_DEFAULT_VALUE_FLAG = 4096; // Field has no default value + ON_UPDATE_NOW_FLAG = 8192; // If a field is updated it will get the current time value (NOW()) + NUM_FLAG = 32768; // Field is numeric + PART_KEY_FLAG = 16384; // wrong from here on, where do these come from? + GROUP_FLAG = 32768; + UNIQUE_FLAG = 65536; + BINCMP_FLAG = 131072; + + // Client connection options + CLIENT_LONG_PASSWORD: Int64 = 0; // obsolete flag + CLIENT_MYSQL: Int64 = 1; // mysql/old mariadb server/client + CLIENT_FOUND_ROWS: Int64 = 2; // Found instead of affected rows + CLIENT_LONG_FLAG: Int64 = 4; // Get all column flags + CLIENT_CONNECT_WITH_DB: Int64 = 8; // One can specify db on connect + CLIENT_NO_SCHEMA: Int64 = 16; // Don't allow database.table.column + CLIENT_COMPRESS: Int64 = 32; // Can use compression protocol + CLIENT_ODBC: Int64 = 64; // Odbc client + CLIENT_LOCAL_FILES: Int64 = 128; // Can use LOAD DATA LOCAL + CLIENT_IGNORE_SPACE: Int64 = 256; // Ignore spaces before '(' + CLIENT_PROTOCOL_41: Int64 = 512; // New 4.1 protocol + CLIENT_INTERACTIVE: Int64 = 1024; // This is an interactive client + CLIENT_SSL: Int64 = 2048; // Switch to SSL after handshake + CLIENT_IGNORE_SIGPIPE: Int64 = 4096; // IGNORE sigpipes + CLIENT_TRANSACTIONS: Int64 = 8192; // Client knows about transactions + CLIENT_RESERVED: Int64 = 16384; // Old flag for 4.1 protocol + CLIENT_SECURE_CONNECTION: Int64 = 32768; // New 4.1 authentication + CLIENT_MULTI_STATEMENTS: Int64 = 1 Shl 16; // Enable/disable multi-stmt support + CLIENT_MULTI_RESULTS: Int64 = 1 Shl 17; // Enable/disable multi-results + CLIENT_PS_MULTI_RESULTS: Int64 = 1 Shl 18; // Multi-results in PS-protocol + CLIENT_PLUGIN_AUTH: Int64 = 1 Shl 19; // Client supports plugin authentication + CLIENT_CONNECT_ATTRS: Int64 = 1 Shl 20; // Client supports connection attributes + // Enable authentication response packet to be larger than 255 bytes. + CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA: Int64 = 1 Shl 21; + // Don't close the connection for a connection with expired password. + CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS: Int64 = 1 Shl 22; + { + Capable of handling server state change information. Its a hint to the + server to include the state change information in Ok packet. + } + CLIENT_SESSION_TRACK: Int64 = 1 Shl 23; + // Client no longer needs EOF packet + CLIENT_DEPRECATE_EOF: Int64 = 1 Shl 24; + CLIENT_PROGRESS_OBSOLETE: Int64 = 1 Shl 29; + CLIENT_SSL_VERIFY_SERVER_CERT: Int64 = 1 Shl 30; + { + It used to be that if mysql_real_connect() failed, it would delete any + options set by the client, unless the CLIENT_REMEMBER_OPTIONS flag was + given. + That behaviour does not appear very useful, and it seems unlikely that + any applications would actually depend on this. So from MariaDB 5.5 we + always preserve any options set in case of failed connect, and this + option is effectively always set. + } + CLIENT_REMEMBER_OPTIONS: Int64 = 1 Shl 31; + + COLLATION_BINARY = 63; + // Equivalent to COLLATION_BINARY, this is what a new driver returns when connected to a pre-4.1 server. + COLLATION_NONE = 0; + + // Relevant MySQL error codes, taken from include/mysql/server/mysqld_error.h + ER_MUST_CHANGE_PASSWORD = 1820; + ER_NO_SUCH_THREAD = 1094; + ER_NONEXISTING_GRANT = 1141; + ER_WRONG_AUTO_KEY = 1075; + +type + PUSED_MEM=^USED_MEM; + USED_MEM = packed record + next: PUSED_MEM; + left: Integer; + size: Integer; + end; + + PERR_PROC = ^ERR_PROC; + ERR_PROC = procedure; + + PMEM_ROOT = ^MEM_ROOT; + MEM_ROOT = packed record + free: PUSED_MEM; + used: PUSED_MEM; + pre_alloc: PUSED_MEM; + min_malloc: Integer; + block_size: Integer; + block_num: Integer; + first_block_usage: Integer; + error_handler: PERR_PROC; + end; + + NET = record + vio: Pointer; + buff: PAnsiChar; + buff_end: PAnsiChar; + write_pos: PAnsiChar; + read_pos: PAnsiChar; + fd: Integer; + max_packet: Cardinal; + max_packet_size: Cardinal; + pkt_nr: Cardinal; + compress_pkt_nr: Cardinal; + write_timeout: Cardinal; + read_timeout: Cardinal; + retry_count: Cardinal; + fcntl: Integer; + compress: Byte; + remain_in_buf: LongInt; + length: LongInt; + buf_length: LongInt; + where_b: LongInt; + return_status: Pointer; + reading_or_writing: Char; + save_char: Char; + no_send_ok: Byte; + last_error: array[1..MYSQL_ERRMSG_SIZE] of Char; + sqlstate: array[1..SQLSTATE_LENGTH + 1] of Char; + last_errno: Cardinal; + error: Char; + query_cache_query: Pointer; + report_error: Byte; + return_errno: Byte; + end; + + PMYSQL_FIELD = ^MYSQL_FIELD; + MYSQL_FIELD = record + name: PAnsiChar; // Name of column + org_name: PAnsiChar; // Name of original column (added after 3.23.58) + table: PAnsiChar; // Table of column if column was a field + org_table: PAnsiChar; // Name of original table (added after 3.23.58 + db: PAnsiChar; // table schema (added after 3.23.58) + catalog: PAnsiChar; // table catalog (added after 3.23.58) + def: PAnsiChar; // Default value (set by mysql_list_fields) + length: LongInt; // Width of column + max_length: LongInt; // Max width of selected set + // added after 3.23.58 + name_length: Cardinal; + org_name_length: Cardinal; + table_length: Cardinal; + org_table_length: Cardinal; + db_length: Cardinal; + catalog_length: Cardinal; + def_length: Cardinal; + //*********************** + flags: Cardinal; // Div flags + decimals: Cardinal; // Number of decimals in field + charsetnr: Cardinal; // char set number (added in 4.1) + _type: Cardinal; // Type of field. Se mysql_com.h for types + end; + + // Added in Oct 2023, to fix usage of mysql_fetch_lengths(). See issue #1863 + PMYSQL_LENGTHS = ^TMYSQL_LENGTHS; + TMYSQL_LENGTHS = array[0..MaxInt div SizeOf(LongWord) - 1] of LongWord; + + MYSQL_ROW = array[0..$ffff] of PAnsiChar; + PMYSQL_ROW = ^MYSQL_ROW; + + PMYSQL_ROWS = ^MYSQL_ROWS; + MYSQL_ROWS = record + next: PMYSQL_ROWS; + data: PMYSQL_ROW; + end; + + MYSQL_DATA = record + Rows: Int64; + Fields: Cardinal; + Data: PMYSQL_ROWS; + Alloc: MEM_ROOT; + end; + PMYSQL_DATA = ^MYSQL_DATA; + + PMYSQL = ^MYSQL; + MYSQL = record + _net: NET; + connector_fd: Pointer; + host: PAnsiChar; + user: PAnsiChar; + passwd: PAnsiChar; + unix_socket: PAnsiChar; + server_version: PAnsiChar; + host_info: PAnsiChar; + info: PAnsiChar; + db: PAnsiChar; + charset: PAnsiChar; + fields: PMYSQL_FIELD; + field_alloc: MEM_ROOT; + affected_rows: Int64; + insert_id: Int64; + extra_info: Int64; + thread_id: LongInt; + packet_length: LongInt; + port: Cardinal; + client_flag: LongInt; + server_capabilities: LongInt; + protocol_version: Cardinal; + field_count: Cardinal; + server_status: Cardinal; + server_language: Cardinal; + warning_count: Cardinal; + options: Cardinal; + status: Byte; + free_me: Byte; + reconnect: Byte; + scramble: array[1..SCRAMBLE_LENGTH+1] of Char; + rpl_pivot: Byte; + master: PMYSQL; + next_slave: PMYSQL; + last_used_slave: PMYSQL; + last_used_con: PMYSQL; + stmts: Pointer; + methods: Pointer; + thd: Pointer; + unbuffered_fetch_owner: PByte; + end; + + MYSQL_RES = record + row_count: Int64; + field_count, current_field: Integer; + fields: PMYSQL_FIELD; + data: PMYSQL_DATA; + data_cursor: PMYSQL_ROWS; + field_alloc: MEM_ROOT; + row: PMYSQL_ROW; // If unbuffered read + current_row: PMYSQL_ROW; // buffer to current row + lengths: PLongInt; // column lengths of current row + handle: PMYSQL; // for unbuffered reads + eof: Byte; // Used my mysql_fetch_row + is_ps: Byte; + end; + PMYSQL_RES = ^MYSQL_RES; + + Tmgci = function: PAnsiChar; stdcall; + TMySQLLib = class(TDbLib) + mysql_affected_rows: function(Handle: PMYSQL): Int64; stdcall; + mysql_character_set_name: function(Handle: PMYSQL): PAnsiChar; stdcall; + mysql_close: procedure(Handle: PMYSQL); stdcall; + mysql_data_seek: procedure(Result: PMYSQL_RES; Offset: Int64); stdcall; + mysql_errno: function(Handle: PMYSQL): Cardinal; stdcall; + mysql_error: function(Handle: PMYSQL): PAnsiChar; stdcall; + mysql_fetch_field_direct: function(Result: PMYSQL_RES; FieldNo: Cardinal): PMYSQL_FIELD; stdcall; + mysql_fetch_field: function(Result: PMYSQL_RES): PMYSQL_FIELD; stdcall; + mysql_fetch_lengths: function(Result: PMYSQL_RES): PMYSQL_LENGTHS; stdcall; + mysql_fetch_row: function(Result: PMYSQL_RES): PMYSQL_ROW; stdcall; + mysql_free_result: procedure(Result: PMYSQL_RES); stdcall; + mysql_get_client_info: function: PAnsiChar; stdcall; + mysql_get_server_info: function(Handle: PMYSQL): PAnsiChar; stdcall; + mysql_init: function(Handle: PMYSQL): PMYSQL; stdcall; + mysql_info: function(Handle: PMYSQL): PAnsiChar; stdcall; + mysql_num_fields: function(Result: PMYSQL_RES): Integer; stdcall; + mysql_num_rows: function(Result: PMYSQL_RES): Int64; stdcall; + mysql_options: function(Handle: PMYSQL; Option: Integer; arg: PAnsiChar): Integer; stdcall; + mysql_optionsv: function(Handle: PMYSQL; Option: Integer; arg, val: PAnsiChar): Integer; stdcall; + mysql_ping: function(Handle: PMYSQL): Integer; stdcall; + mysql_real_connect: function(Handle: PMYSQL; const Host, User, Passwd, Db: PAnsiChar; Port: Cardinal; const UnixSocket: PAnsiChar; ClientFlag: Cardinal): PMYSQL; stdcall; + mysql_real_query: function(Handle: PMYSQL; const Query: PAnsiChar; Length: Cardinal): Integer; stdcall; + mysql_stat: function(Handle: PMYSQL): PAnsiChar; stdcall; + mysql_store_result: function(Handle: PMYSQL): PMYSQL_RES; stdcall; + mysql_thread_id: function(Handle: PMYSQL): Cardinal; stdcall; + mysql_next_result: function(Handle: PMYSQL): Integer; stdcall; + mysql_set_character_set: function(Handle: PMYSQL; csname: PAnsiChar): Integer; stdcall; + mysql_thread_init: function: Byte; stdcall; + mysql_thread_end: procedure; stdcall; + mysql_warning_count: function(Handle: PMYSQL): Cardinal; stdcall; + const + INVALID_OPT = -1; + MYBOOL_FALSE: Byte = 0; + MYBOOL_TRUE: Byte = 1; + protected + procedure AssignProcedures; override; + public + MYSQL_OPT_LOCAL_INFILE, + MYSQL_OPT_CONNECT_TIMEOUT, + MARIADB_OPT_TLS_VERSION, + MYSQL_OPT_TLS_VERSION, + MYSQL_PLUGIN_DIR, + MYSQL_OPT_SSL_KEY, + MYSQL_OPT_SSL_CERT, + MYSQL_OPT_SSL_CA, + MYSQL_OPT_SSL_CIPHER, + MYSQL_OPT_CONNECT_ATTR_ADD, + MYSQL_ENABLE_CLEARTEXT_PLUGIN, + MYSQL_OPT_SSL_MODE, + MYSQL_OPT_SSL_VERIFY_SERVER_CERT: Integer; + SSL_MODE_DISABLED, + SSL_MODE_PREFERRED, + SSL_MODE_REQUIRED, + SSL_MODE_VERIFY_CA, + SSL_MODE_VERIFY_IDENTITY: Integer; + constructor Create(DllFile_, DefaultDll: String); override; + end; +var + MySQLKeywords: TStringList; + MySQLErrorCodes: TStringList; + + + // MySQL Data Type List and Properties + MySQLDatatypes: array of TDBDatatype; + {MySQLDatatypes: array [0..41] of TDBDatatype = + ( + ( + Index: dbdtUnknown; + NativeTypes: '99999'; + Name: 'UNKNOWN'; + Description: 'Unknown data type'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcOther; + ), + ( + Index: dbdtTinyint; + NativeType: 1; + Name: 'TINYINT'; + Description: 'TINYINT[(M)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A very small integer. The signed range is -128 to 127. ' + + 'The unsigned range is 0 to 255.'; + HasLength: True; + RequiresLength: False; + MaxSize: 127; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), + ( + Index: dbdtSmallint; + NativeType: 2; + Name: 'SMALLINT'; + Description: 'SMALLINT[(M)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A small integer. The signed range is -32768 to 32767. ' + + 'The unsigned range is 0 to 65535.'; + HasLength: True; + RequiresLength: False; + MaxSize: 32767; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), + ( + Index: dbdtMediumint; + NativeType: 9; + Name: 'MEDIUMINT'; + Description: 'MEDIUMINT[(M)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A medium-sized integer. The signed range is -8388608 to 8388607. ' + + 'The unsigned range is 0 to 16777215.'; + HasLength: True; + RequiresLength: False; + MaxSize: 8388607; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), + ( + Index: dbdtInt; + NativeType: 3; + Name: 'INT'; + Description: 'INT[(M)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A normal-size integer. The signed range is -2147483648 to 2147483647. ' + + 'The unsigned range is 0 to 4294967295.'; + HasLength: True; + RequiresLength: False; + MaxSize: 2147483647; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), + ( + Index: dbdtBigint; + NativeType: 8; + Name: 'BIGINT'; + Description: 'BIGINT[(M)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A large integer. The signed range is -9223372036854775808 to ' + + '9223372036854775807. The unsigned range is 0 to 18446744073709551615.'; + HasLength: True; + RequiresLength: False; + MaxSize: 9223372036854775807; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), + ( + Index: dbdtFloat; + NativeType: 4; + Name: 'FLOAT'; + Description: 'FLOAT[(M,D)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A small (single-precision) floating-point number. Allowable values are '+ + '-3.402823466E+38 to -1.175494351E-38, 0, and 1.175494351E-38 to '+ + '3.402823466E+38. These are the theoretical limits, based on the IEEE '+ + 'standard. The actual range might be slightly smaller depending on your '+ + 'hardware or operating system.'; + HasLength: True; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcReal; + ), + ( + Index: dbdtDouble; + NativeType: 5; + Name: 'DOUBLE'; + Description: 'DOUBLE[(M,D)] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A normal-size (double-precision) floating-point number. Allowable ' + + 'values are -1.7976931348623157E+308 to -2.2250738585072014E-308, 0, and ' + + '2.2250738585072014E-308 to 1.7976931348623157E+308. These are the ' + + 'theoretical limits, based on the IEEE standard. The actual range might ' + + 'be slightly smaller depending on your hardware or operating system.'; + HasLength: True; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcReal; + ), + ( + Index: dbdtDecimal; + NativeType: 246; + Name: 'DECIMAL'; + Description: 'DECIMAL[(M[,D])] [UNSIGNED] [ZEROFILL]' + sLineBreak + + 'A packed "exact" fixed-point number. M is the total number of digits ' + + '(the precision) and D is the number of digits after the decimal point ' + + '(the scale). The decimal point and (for negative numbers) the "-" sign ' + + 'are not counted in M. If D is 0, values have no decimal point or ' + + 'fractional part. The maximum number of digits (M) for DECIMAL is 65. ' + + 'The maximum number of supported decimals (D) is 30. If D is omitted, ' + + 'the default is 0. If M is omitted, the default is 10.'; + HasLength: True; + RequiresLength: True; + MaxSize: 9223372036854775807; + HasBinary: False; + HasDefault: True; + LoadPart: False; + DefLengthSet: '20,6'; + Category: dtcReal; + ), + ( + Index: dbdtDate; + NativeType: 10; + Name: 'DATE'; + Description: 'DATE' + sLineBreak + + 'A date. The supported range is ''1000-01-01'' to ''9999-12-31''. MySQL ' + + 'displays DATE values in ''YYYY-MM-DD'' format, but allows assignment of ' + + 'values to DATE columns using either strings or numbers.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Format: 'yyyy-mm-dd'; + Category: dtcTemporal; + ), + ( + Index: dbdtTime; + NativeType: 11; + Name: 'TIME'; + Description: 'TIME' + sLineBreak + + 'A time. The range is ''-838:59:59'' to ''838:59:59''. MySQL displays TIME ' + + 'values in ''HH:MM:SS'' format, but allows assignment of values to TIME ' + + 'columns using either strings or numbers.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Format: 'hh:nn:ss'; + Category: dtcTemporal; + ), + ( + Index: dbdtYear; + NativeType: 13; + Name: 'YEAR'; + Description: 'YEAR[(2|4)]' + sLineBreak + + 'A year in two-digit or four-digit format. The default is four-digit ' + + 'format. In four-digit format, the allowable values are 1901 to 2155, ' + + 'and 0000. In two-digit format, the allowable values are 70 to 69, ' + + 'representing years from 1970 to 2069. MySQL displays YEAR values in ' + + 'YYYY format, but allows you to assign values to YEAR columns using ' + + 'either strings or numbers.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Format: 'yyyy'; + Category: dtcTemporal; + ), + ( + Index: dbdtDatetime; + NativeType: 12; + Name: 'DATETIME'; + Description: 'DATETIME' + sLineBreak + + 'A date and time combination. The supported range is ''1000-01-01 ' + + '00:00:00'' to ''9999-12-31 23:59:59''. MySQL displays DATETIME values in ' + + '''YYYY-MM-DD HH:MM:SS'' format, but allows assignment of values to ' + + 'DATETIME columns using either strings or numbers.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Format: 'yyyy-mm-dd hh:nn:ss'; + Category: dtcTemporal; + ), + ( + Index: dbdtTimestamp; + NativeType: 7; + Name: 'TIMESTAMP'; + Description: 'TIMESTAMP' + sLineBreak + + 'A timestamp. The range is ''1970-01-01 00:00:01'' UTC to ''2038-01-09 ' + + '03:14:07'' UTC. TIMESTAMP values are stored as the number of seconds ' + + 'since the epoch (''1970-01-01 00:00:00'' UTC). A TIMESTAMP cannot ' + + 'represent the value ''1970-01-01 00:00:00'' because that is equivalent to ' + + '0 seconds from the epoch and the value 0 is reserved for representing ' + + '''0000-00-00 00:00:00'', the "zero" TIMESTAMP value.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Format: 'yyyy-mm-dd hh:nn:ss'; + Category: dtcTemporal; + ), + ( + Index: dbdtVarchar; + NativeType: 253; + Name: 'VARCHAR'; + Description: 'VARCHAR(M)' + sLineBreak + + 'A variable-length string. M represents the maximum column length in ' + + 'characters. The range of M is 0 to 65,535. The effective maximum length ' + + 'of a VARCHAR is subject to the maximum row size (65,535 bytes, which is ' + + 'shared among all columns) and the character set used. For example, utf8 ' + + 'characters can require up to three bytes per character, so a VARCHAR ' + + 'column that uses the utf8 character set can be declared to be a maximum ' + + 'of 21,844 characters. ' + sLineBreak + sLineBreak + + '*Note*: MySQL 5.1 follows the standard SQL specification, and does not ' + + 'remove trailing spaces from VARCHAR values.'; + HasLength: True; + RequiresLength: True; + MaxSize: 255; + HasBinary: True; // MySQL-Help says the opposite but it's valid for older versions at least. + HasDefault: True; + LoadPart: True; + DefLengthSet: '50'; + Category: dtcText; + ), + ( + Index: dbdtChar; + NativeType: 254; + Name: 'CHAR'; + Description: 'CHAR[(M)]' + sLineBreak + + 'A fixed-length string that is always right-padded with spaces to the ' + + 'specified length when stored. M represents the column length in ' + + 'characters. The range of M is 0 to 255. If M is omitted, the length is 1.' + sLineBreak + sLineBreak + + '*Note*: Trailing spaces are removed when CHAR values are retrieved ' + + 'unless the PAD_CHAR_TO_FULL_LENGTH SQL mode is enabled.'; + HasLength: True; + RequiresLength: True; + MaxSize: 255; + HasBinary: True; + HasDefault: True; + LoadPart: False; + DefLengthSet: '50'; + Category: dtcText; + ), + ( + Index: dbdtTinytext; + NativeType: 249; + Name: 'TINYTEXT'; + Description: 'TINYTEXT' + sLineBreak + + 'A TEXT column with a maximum length of 255 (2^8 - 1) characters. The ' + + 'effective maximum length is less if the value contains multi-byte ' + + 'characters. Each TINYTEXT value is stored using a one-byte length ' + + 'prefix that indicates the number of bytes in the value.'; + HasLength: False; + RequiresLength: False; + MaxSize: 255; + HasBinary: True; + HasDefault: False; + LoadPart: False; + Category: dtcText; + ), + ( + Index: dbdtText; + NativeType: 252; + Name: 'TEXT'; + Description: 'TEXT[(M)]' + sLineBreak + + 'A TEXT column with a maximum length of 65,535 (2^16 - 1) characters. The ' + + 'effective maximum length is less if the value contains multi-byte ' + + 'characters. Each TEXT value is stored using a two-byte length prefix ' + + 'that indicates the number of bytes in the value. ' + sLineBreak + + 'An optional length M can be given for this type. If this is done, MySQL ' + + 'creates the column as the smallest TEXT type large enough to hold ' + + 'values M characters long.'; + HasLength: True; + RequiresLength: False; + MaxSize: 65535; + DefaultSize: 65535; + HasBinary: True; + HasDefault: False; + LoadPart: True; + Category: dtcText; + ), + ( + Index: dbdtMediumtext; + NativeType: 250; + Name: 'MEDIUMTEXT'; + Description: 'MEDIUMTEXT' + sLineBreak + + 'A TEXT column with a maximum length of 16,777,215 (2^24 - 1) characters. ' + + 'The effective maximum length is less if the value contains multi-byte ' + + 'characters. Each MEDIUMTEXT value is stored using a three-byte length ' + + 'prefix that indicates the number of bytes in the value.'; + HasLength: False; + RequiresLength: False; + HasBinary: True; + HasDefault: False; + LoadPart: True; + Category: dtcText; + ), + ( + Index: dbdtLongtext; + NativeType: 251; + Name: 'LONGTEXT'; + Description: 'LONGTEXT' + sLineBreak + + 'A TEXT column with a maximum length of 4,294,967,295 or 4GB (2^32 - 1) ' + + 'characters. The effective maximum length is less if the value contains ' + + 'multi-byte characters. The effective maximum length of LONGTEXT columns ' + + 'also depends on the configured maximum packet size in the client/server ' + + 'protocol and available memory. Each LONGTEXT value is stored using a ' + + 'four-byte length prefix that indicates the number of bytes in the ' + + 'value.'; + HasLength: False; + RequiresLength: False; + HasBinary: True; + HasDefault: False; + LoadPart: True; + Category: dtcText; + ), + ( + Index: dbdtJson; + NativeType: 245; + Name: 'JSON'; + Description: 'JSON' + sLineBreak + + 'Documents stored in JSON columns are converted to an internal format that '+ + 'permits quick read access to document elements. When the server later must '+ + 'read a JSON value stored in this binary format, the value need not be parsed '+ + 'from a text representation. The binary format is structured to enable the '+ + 'server to look up subobjects or nested values directly by key or array index '+ + 'without reading all values before or after them in the document.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: True; + Category: dtcText; + ), + ( + Index: dbdtUniqueidentifier; + NativeType: 254; + Name: 'UUID'; + Description: 'UUID' + sLineBreak + + 'The UUID data type is intended for the storage of 128-bit UUID (Universally ' + + 'Unique Identifier) data. See the UUID function page for more details on UUIDs ' + + 'themselves.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcText; + ), + ( + Index: dbdtInet4; + NativeType: 255; + Name: 'INET4'; + Description: 'INET4' + sLineBreak + + 'INET4 is a data type to store IPv4 addresses, as 4-byte binary strings. '+ + 'It was added in MariaDB 10.10.0'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcText; + MinVersion: 10100; + ), + ( + Index: dbdtInet6; + NativeType: 255; + Name: 'INET6'; + Description: 'INET6' + sLineBreak + + 'The INET6 data type is intended for storage of IPv6 addresses, as well as ' + + 'IPv4 addresses assuming conventional mapping of IPv4 addresses into IPv6 ' + + 'addresses. ' + slineBreak + + 'Both short and long IPv6 notation are permitted, according to RFC-5952. '+ + 'It was added in MariaDB 10.5.0'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcText; + MinVersion: 10050; + ), + ( + Index: dbdtBinary; + NativeType: 254; + Name: 'BINARY'; + Description: 'BINARY(M)' + sLineBreak + + 'The BINARY type is similar to the CHAR type, but stores binary byte ' + + 'strings rather than non-binary character strings. M represents the ' + + 'column length in bytes.'; + HasLength: True; + RequiresLength: True; + HasBinary: False; + HasDefault: True; + LoadPart: False; + DefLengthSet: '50'; + Category: dtcBinary; + ), + ( + Index: dbdtVarbinary; + NativeType: 253; + Name: 'VARBINARY'; + Description: 'VARBINARY(M)' + sLineBreak + + 'The VARBINARY type is similar to the VARCHAR type, but stores binary ' + + 'byte strings rather than non-binary character strings. M represents the ' + + 'maximum column length in bytes.'; + HasLength: True; + RequiresLength: True; + HasBinary: False; + HasDefault: True; + LoadPart: True; + DefLengthSet: '50'; + Category: dtcBinary; + ), + ( + Index: dbdtTinyblob; + NativeType: 249; + Name: 'TINYBLOB'; + Description: 'TINYBLOB' + sLineBreak + + 'A BLOB column with a maximum length of 255 (2^8 - 1) bytes. Each ' + + 'TINYBLOB value is stored using a one-byte length prefix that indicates ' + + 'the number of bytes in the value.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcBinary; + ), + ( + Index: dbdtBlob; + NativeType: 252; + Name: 'BLOB'; + Description: 'BLOB[(M)]' + sLineBreak + + 'A BLOB column with a maximum length of 65,535 (2^16 - 1) bytes. Each ' + + 'BLOB value is stored using a two-byte length prefix that indicates the ' + + 'number of bytes in the value. ' + sLineBreak + + 'An optional length M can be given for this type. If this is done, MySQL ' + + 'creates the column as the smallest BLOB type large enough to hold ' + + 'values M bytes long.'; + HasLength: True; + RequiresLength: False; + MaxSize: 65535; + DefaultSize: 65535; + HasBinary: False; + HasDefault: False; + LoadPart: True; + Category: dtcBinary; + ), + ( + Index: dbdtMediumblob; + NativeType: 250; + Name: 'MEDIUMBLOB'; + Description: 'MEDIUMBLOB' + sLineBreak + + 'A BLOB column with a maximum length of 16,777,215 (2^24 - 1) bytes. Each ' + + 'MEDIUMBLOB value is stored using a three-byte length prefix that ' + + 'indicates the number of bytes in the value.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: True; + Category: dtcBinary; + ), + ( + Index: dbdtLongblob; + NativeType: 251; + Name: 'LONGBLOB'; + Description: 'LONGBLOB' + sLineBreak + + 'A BLOB column with a maximum length of 4,294,967,295 or 4GB (2^32 - 1) ' + + 'bytes. The effective maximum length of LONGBLOB columns depends on the ' + + 'configured maximum packet size in the client/server protocol and ' + + 'available memory. Each LONGBLOB value is stored using a four-byte ' + + 'length prefix that indicates the number of bytes in the value.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: True; + Category: dtcBinary; + ), + ( + Index: dbdtVector; + NativeType: 253; + Name: 'VECTOR'; + Description: 'VECTOR(N)' + sLineBreak + + 'VECTOR data type with a built-in data validation. N is the number of dimensions ' + + 'that all vector values in the column will have.'; + HasLength: True; + RequiresLength: True; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcBinary; + ), + ( + Index: dbdtEnum; + NativeType: 247; + Name: 'ENUM'; + Description: 'ENUM(''value1'',''value2'',...)' + sLineBreak + + 'An enumeration. A string object that can have only one value, chosen ' + + 'from the list of values ''value1'', ''value2'', ..., NULL or the special '''' ' + + 'error value. An ENUM column can have a maximum of 65,535 distinct ' + + 'values. ENUM values are represented internally as integers.'; + HasLength: True; // Obviously this is not meant as "length", but as "set of values" + RequiresLength: True; + HasBinary: False; + HasDefault: True; + LoadPart: False; + DefLengthSet: '''Y'',''N'''; + Category: dtcOther; + ), + ( + Index: dbdtSet; + NativeType: 248; + Name: 'SET'; + Description: 'SET(''value1'',''value2'',...)' + sLineBreak + + 'A set. A string object that can have zero or more values, each of which ' + + 'must be chosen from the list of values ''value1'', ''value2'', ... A SET ' + + 'column can have a maximum of 64 members. SET values are represented ' + + 'internally as integers.'; + HasLength: True; // Same as for ENUM + RequiresLength: True; + HasBinary: False; + HasDefault: True; + LoadPart: False; + DefLengthSet: '''Value A'',''Value B'''; + Category: dtcOther; + ), + ( + Index: dbdtBit; + NativeType: 16; + Name: 'BIT'; + Description: 'BIT[(M)]' + sLineBreak + + 'A bit-field type. M indicates the number of bits per value, from 1 to ' + + '64. The default is 1 if M is omitted.'; + HasLength: True; + RequiresLength: False; + HasBinary: False; + HasDefault: True; + LoadPart: False; + Category: dtcInteger; + ), + ( + Index: dbdtPoint; + NativeType: 255; + Name: 'POINT'; + Description: 'POINT(x,y)' + sLineBreak + + 'Constructs a WKB Point using its coordinates.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtLinestring; + NativeType: 255; + Name: 'LINESTRING'; + Description: 'LINESTRING(pt1,pt2,...)' + sLineBreak + + 'Constructs a WKB LineString value from a number of WKB Point arguments. ' + + 'If any argument is not a WKB Point, the return value is NULL. If the ' + + 'number of Point arguments is less than two, the return value is NULL.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtPolygon; + NativeType: 255; + Name: 'POLYGON'; + Description: 'POLYGON(ls1,ls2,...)' + sLineBreak + + 'Constructs a WKB Polygon value from a number of WKB LineString ' + + 'arguments. If any argument does not represent the WKB of a LinearRing ' + + '(that is, not a closed and simple LineString) the return value is NULL.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtGeometry; + NativeType: 255; + Name: 'GEOMETRY'; + Description: ''; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtMultipoint; + NativeType: 255; + Name: 'MULTIPOINT'; + Description: 'MULTIPOINT(pt1,pt2,...)' + sLineBreak + + 'Constructs a WKB MultiPoint value using WKB Point arguments. If any ' + + 'argument is not a WKB Point, the return value is NULL.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtMultilinestring; + NativeType: 255; + Name: 'MULTILINESTRING'; + Description: 'MULTILINESTRING(ls1,ls2,...)' + sLineBreak + + 'Constructs a WKB MultiLineString value using WKB LineString arguments. ' + + 'If any argument is not a WKB LineString, the return value is NULL.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtMultipolygon; + NativeType: 255; + Name: 'MULTIPOLYGON'; + Description: 'MULTIPOLYGON(poly1,poly2,...)' + sLineBreak + + 'Constructs a WKB MultiPolygon value from a set of WKB Polygon ' + + 'arguments. If any argument is not a WKB Polygon, the return value is ' + + 'NULL.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ), + ( + Index: dbdtGeometrycollection; + NativeType: 255; + Name: 'GEOMETRYCOLLECTION'; + Description: 'GEOMETRYCOLLECTION(g1,g2,...)' + sLineBreak + + 'Constructs a WKB GeometryCollection. If any argument is not a ' + + 'well-formed WKB representation of a geometry, the return value is NULL.'; + HasLength: False; + RequiresLength: False; + HasBinary: False; + HasDefault: False; + LoadPart: False; + Category: dtcSpatial; + ) + + );} + + + MySQLVariables: array [0..417] of TServerVariable = + ( + ( + Name: 'auto_increment_increment'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'auto_increment_offset'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'autocommit'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'automatic_sp_privileges'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'back_log'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'basedir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'big_tables'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'binlog_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'binlog_checksum'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'binlog_direct_non_transactional_updates'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'binlog_format'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'ROW,STATEMENT,MIXED'; + ), + ( + Name: 'binlog_row_image'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'FULL,MINIMAL,NOBLOB'; + ), + ( + Name: 'binlog_stmt_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'bulk_insert_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_client'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_connection'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_database[a]'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_filesystem'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_results'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_server'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'character_set_system'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'character_sets_dir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'collation_connection'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'collation_database[b]'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'collation_server'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'completion_type'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'NO_CHAIN,CHAIN,RELEASE,0,1,2'; + ), + ( + Name: 'concurrent_insert'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: 'NEVER,AUTO,ALWAYS,0,1,2'; + ), + ( + Name: 'connect_timeout'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'datadir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'date_format'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'datetime_format'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'debug'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'debug_sync'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'default_storage_engine'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'FEDERATED,MRG_MYISAM,MyISAM,BLACKHOLE,CSV,MEMORY,ARCHIVE,InnoDB'; + ), + ( + Name: 'default_tmp_storage_engine'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'FEDERATED,MRG_MYISAM,MyISAM,BLACKHOLE,CSV,MEMORY,ARCHIVE,InnoDB'; + ), + ( + Name: 'default_week_format'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'delay_key_write'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: 'ON,OFF,ALL'; + ), + ( + Name: 'delayed_insert_limit'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'delayed_insert_timeout'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'delayed_queue_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'disable_gtid_unsafe_statements'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'div_precision_increment'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'end_markers_in_json'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'engine_condition_pushdown'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'eq_range_index_dive_limit'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'error_count'; + IsDynamic: False; + VarScope: vsSession; + ), + ( + Name: 'event_scheduler'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: 'ON,OFF,DISABLED'; + ), + ( + Name: 'expire_logs_days'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'external_user'; + IsDynamic: False; + VarScope: vsSession; + ), + ( + Name: 'flush'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'flush_time'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'foreign_key_checks'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'ft_boolean_syntax'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'ft_max_word_len'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ft_min_word_len'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ft_query_expansion_limit'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ft_stopword_file'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'general_log'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'general_log_file'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'group_concat_max_len'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'gtid_done'; + IsDynamic: False; + VarScope: vsBoth; + ), + ( + Name: 'gtid_lost'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'gtid_mode'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'gtid_mode'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'gtid_next'; + IsDynamic: True; + VarScope: vsSession; + EnumValues: 'AUTOMATIC,ANONYMOUS'; + ), + ( + Name: 'gtid_owned'; + IsDynamic: False; + VarScope: vsBoth; + ), + ( + Name: 'have_compress'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_crypt'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_csv'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_dynamic_loading'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_geometry'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_innodb'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_ndbcluster'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_openssl'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_partitioning'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_profiling'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_query_cache'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_rtree_keys'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_ssl'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'have_symlink'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'host_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'hostname'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'identity'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'ignore_builtin_innodb'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'init_connect'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'init_file'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'init_slave'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_adaptive_flushing'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_adaptive_hash_index'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_adaptive_max_sleep_delay'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_additional_mem_pool_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_analyze_is_persistent'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_api_enable_binlog'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_api_enable_mdl'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_api_trx_level'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_autoextend_increment'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_autoinc_lock_mode'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_dump_at_shutdown'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_dump_now'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_filename'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_load_abort'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_load_at_startup'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_load_now'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_buffer_pool_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_change_buffer_max_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_change_buffering'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: 'INSERTS,DELETES,PURGES,CHANGES,ALL,NONE'; + ), + ( + Name: 'innodb_checksum_algorithm'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: 'INNODB,CRC32,NONE,STRICT_INNODB,STRICT_CRC32,STRICT_NONE'; + ), + ( + Name: 'innodb_checksums'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_commit_concurrency'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_concurrency_tickets'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_data_file_path'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_data_home_dir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_doublewrite'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_fast_shutdown'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_file_format'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_file_format_check'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_file_format_max'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_file_per_table'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_flush_log_at_trx_commit'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: '0,1,2'; + ), + ( + Name: 'innodb_flush_method'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_flush_neighbors'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_force_load_corrupted'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_force_recovery'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_aux_table'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_enable_stopword'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_max_token_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_min_token_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_num_word_optimize'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_server_stopword_table'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_sort_pll_degree'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_ft_user_stopword_table'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_io_capacity'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_large_prefix'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_lock_wait_timeout'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'innodb_locks_unsafe_for_binlog'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_log_buffer_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_log_file_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_log_files_in_group'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_log_group_home_dir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_lru_scan_depth'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_max_dirty_pages_pct'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_max_purge_lag'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_mirrored_log_groups'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_monitor_disable'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_monitor_enable'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_monitor_reset'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_monitor_reset_all'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_old_blocks_pct'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_old_blocks_time'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_open_files'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_optimize_fulltext_only'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_page_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_print_all_deadlocks'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_purge_batch_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_purge_threads'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_random_read_ahead'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_read_ahead_threshold'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_read_io_threads'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_replication_delay'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_rollback_on_timeout'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_rollback_segments'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_sort_buffer_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_spin_wait_delay'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_stats_method'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'NULLS_EQUAL,NULLS_UNEQUAL,NULLS_IGNORED'; + ), + ( + Name: 'innodb_stats_on_metadata'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_stats_persistent_sample_pages'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_stats_sample_pages'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_stats_transient_sample_pages'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_strict_mode'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'innodb_support_xa'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'innodb_sync_array_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_sync_spin_loops'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_table_locks'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'innodb_thread_concurrency'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_thread_sleep_delay'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_undo_directory'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_undo_logs'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_undo_tablespaces'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_use_native_aio'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_use_sys_malloc'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_version'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'innodb_write_io_threads'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'insert_id'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'interactive_timeout'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'join_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'keep_files_on_create'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'key_buffer_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'key_cache_age_threshold'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'key_cache_block_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'key_cache_division_limit'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'language'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'large_files_support'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'large_page_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'large_pages'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'last_insert_id'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'lc_messages'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'lc_messages_dir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'lc_time_names'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'license'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'local_infile'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'lock_wait_timeout'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'locked_in_memory'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'log'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'log_bin'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'log_bin_basename'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'log_error'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'log_output'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'log_queries_not_using_indexes'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'log_slave_updates'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'log_slow_queries'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'log_throttle_queries_not_using_indexes'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'log_warnings'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'long_query_time'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'low_priority_updates'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'lower_case_file_system'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'lower_case_table_names'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'master_info_repository'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'master_verify_checksum'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_allowed_packet'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_binlog_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_binlog_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_binlog_stmt_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_connect_errors'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_connections'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_delayed_threads'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_error_count'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_heap_table_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_insert_delayed_threads'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_join_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_length_for_sort_data'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_prepared_stmt_count'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_relay_log_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'max_seeks_for_key'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_sort_length'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_sp_recursion_depth'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_tmp_tables'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_user_connections'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'max_write_lock_count'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'memlock'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'metadata_locks_cache_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'myisam_data_pointer_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'myisam_max_sort_file_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'myisam_mmap_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'myisam_recover_options'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'myisam_repair_threads'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'myisam_sort_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'myisam_stats_method'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'NULLS_EQUAL,NULLS_UNEQUAL,NULLS_IGNORED'; + ), + ( + Name: 'myisam_use_mmap'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'named_pipe'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'net_buffer_length'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'net_read_timeout'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'net_retry_count'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'net_write_timeout'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'new'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'old'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'old_alter_table'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'old_passwords'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'open_files_limit'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'optimizer_join_cache_level'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_prune_level'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_search_depth'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_switch'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_trace'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_trace_features'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_trace_limit'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_trace_max_mem_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'optimizer_trace_offset'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'have_partitioning'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_accounts_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_digests_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_events_stages_history_long_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_events_stages_history_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_events_statements_history_long_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_events_statements_history_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_events_waits_history_long_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_events_waits_history_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_hosts_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_cond_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_cond_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_file_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_file_handles'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_file_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_mutex_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_mutex_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_rwlock_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_rwlock_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_socket_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_socket_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_stage_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_statement_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_table_handles'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_table_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_thread_classes'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_max_thread_instances'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_setup_actors_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_setup_objects_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'performance_schema_users_size'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'pid_file'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'plugin_dir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'port'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'preload_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'profiling'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'profiling_history_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'protocol_version'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'proxy_user'; + IsDynamic: False; + VarScope: vsSession; + ), + ( + Name: 'pseudo_thread_id'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'query_alloc_block_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'query_cache_limit'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'query_cache_min_res_unit'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'query_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'query_cache_type'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: '0,1,2'; + ), + ( + Name: 'query_cache_wlock_invalidate'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'query_prealloc_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'rand_seed1'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'rand_seed2'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'range_alloc_block_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'read_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'read_only'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'read_rnd_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'relay_log_basename'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'relay_log_index'; + IsDynamic: False; + VarScope: vsBoth; + ), + ( + Name: 'relay_log_index'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'relay_log_info_file'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'relay_log_info_repository'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'relay_log_purge'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'relay_log_recovery'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'relay_log_space_limit'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'report_host'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'report_password'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'report_port'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'report_user'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'rpl_semi_sync_master_enabled'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'rpl_semi_sync_master_timeout'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'rpl_semi_sync_master_trace_level'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'rpl_semi_sync_master_wait_no_slave'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'rpl_semi_sync_slave_enabled'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'rpl_semi_sync_slave_trace_level'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'secure_auth'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'secure_file_priv'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'server_id'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'server_uuid'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'shared_memory'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'shared_memory_base_name'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'skip_external_locking'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'skip_name_resolve'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'skip_networking'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'skip_show_database'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'slave_compressed_protocol'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slave_exec_mode'; + IsDynamic: True; + VarScope: vsGlobal; + EnumValues: 'IDEMPOTENT,STRICT'; + ), + ( + Name: 'slave_load_tmpdir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'slave_net_timeout'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slave_parallel_workers'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slave_skip_errors'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'slave_sql_verify_checksum'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slave_transaction_retries'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slave_type_conversions'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'slow_launch_time'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slow_query_log'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'slow_query_log_file'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'socket'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'sort_buffer_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_auto_is_null'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_big_selects'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_big_tables'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_buffer_result'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_log_bin'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_log_off'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_low_priority_updates'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_max_join_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_mode'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_notes'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_quote_show_create'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_safe_updates'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_select_limit'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'sql_slave_skip_counter'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'sql_warnings'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'ssl_ca'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ssl_capath'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ssl_cert'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ssl_cipher'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ssl_crl'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ssl_crlpath'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'ssl_key'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'storage_engine'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'FEDERATED,MRG_MYISAM,MyISAM,BLACKHOLE,CSV,MEMORY,ARCHIVE,InnoDB'; + ), + ( + Name: 'stored_program_cache'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'sync_binlog'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'sync_frm'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'sync_master_info'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'sync_relay_log'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'sync_relay_log_info'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'system_time_zone'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'table_definition_cache'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'table_open_cache'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'thread_cache_size'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'thread_concurrency'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'thread_handling'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'thread_stack'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'time_format'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'time_zone'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'timed_mutexes'; + IsDynamic: True; + VarScope: vsGlobal; + ), + ( + Name: 'timestamp'; + IsDynamic: True; + VarScope: vsSession; + ), + ( + Name: 'tmp_table_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'tmpdir'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'transaction_alloc_block_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'transaction_prealloc_size'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'tx_isolation'; + IsDynamic: True; + VarScope: vsBoth; + EnumValues: 'READ-UNCOMMITTED,READ-COMMITTED,REPEATABLE-READ,SERIALIZABLE'; + ), + ( + Name: 'tx_read_only'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'unique_checks'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'updatable_views_with_limit'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'version'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'version_comment'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'version_compile_machine'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'version_compile_os'; + IsDynamic: False; + VarScope: vsGlobal; + ), + ( + Name: 'wait_timeout'; + IsDynamic: True; + VarScope: vsBoth; + ), + ( + Name: 'warning_count'; + IsDynamic: False; + VarScope: vsSession; + ) + + ); + + +implementation + +uses apphelpers; + + +constructor TMySQLLib.Create(DllFile_, DefaultDll: String); +var + BaseDll: String; +begin + inherited; + // MYSQL_OPT_* constants + MYSQL_OPT_CONNECT_TIMEOUT := 0; + MYSQL_OPT_LOCAL_INFILE := 8; + MYSQL_PLUGIN_DIR := 22; + MYSQL_OPT_SSL_KEY := 25; + MYSQL_OPT_SSL_CERT := 26; + MYSQL_OPT_SSL_CA := 27; + MYSQL_OPT_SSL_CIPHER := 29; + MYSQL_OPT_CONNECT_ATTR_ADD := 33; + MYSQL_ENABLE_CLEARTEXT_PLUGIN := 36; + MYSQL_OPT_TLS_VERSION := 41; + MARIADB_OPT_TLS_VERSION := INVALID_OPT; + MYSQL_OPT_SSL_MODE := INVALID_OPT; + MYSQL_OPT_SSL_VERIFY_SERVER_CERT := INVALID_OPT; + // Option values + SSL_MODE_DISABLED := 1; + SSL_MODE_PREFERRED := 2; + SSL_MODE_REQUIRED := 3; + SSL_MODE_VERIFY_CA := 4; + SSL_MODE_VERIFY_IDENTITY := 5; + BaseDll := ExtractFileName(FDllFile); + if BaseDLL.StartsWith('libmariadb', True) then begin + // Differences in libmariadb + MYSQL_OPT_SSL_VERIFY_SERVER_CERT := 21; + MARIADB_OPT_TLS_VERSION := 7005; + end + else if String(mysql_get_client_info).StartsWith('8.') then begin + // Some constants were removed in MySQL 8.0, so the offsets differ + MYSQL_PLUGIN_DIR := 16; + MYSQL_OPT_SSL_KEY := 19; + MYSQL_OPT_SSL_CERT := 20; + MYSQL_OPT_SSL_CA := 21; + MYSQL_OPT_SSL_CIPHER := 23; + MYSQL_OPT_CONNECT_ATTR_ADD := 27; + MYSQL_ENABLE_CLEARTEXT_PLUGIN := 30; + MYSQL_OPT_TLS_VERSION := 34; + MYSQL_OPT_SSL_MODE := 35; + end; +end; + +procedure TMySQLLib.AssignProcedures; +begin + AssignProc(@mysql_affected_rows, 'mysql_affected_rows'); + AssignProc(@mysql_character_set_name, 'mysql_character_set_name'); + AssignProc(@mysql_close, 'mysql_close'); + AssignProc(@mysql_data_seek, 'mysql_data_seek'); + AssignProc(@mysql_errno, 'mysql_errno'); + AssignProc(@mysql_error, 'mysql_error'); + AssignProc(@mysql_fetch_field_direct, 'mysql_fetch_field_direct'); + AssignProc(@mysql_fetch_field, 'mysql_fetch_field'); + AssignProc(@mysql_fetch_lengths, 'mysql_fetch_lengths'); + AssignProc(@mysql_fetch_row, 'mysql_fetch_row'); + AssignProc(@mysql_free_result, 'mysql_free_result'); + AssignProc(@mysql_get_client_info, 'mysql_get_client_info'); + AssignProc(@mysql_get_server_info, 'mysql_get_server_info'); + AssignProc(@mysql_init, 'mysql_init'); + AssignProc(@mysql_info, 'mysql_info'); + AssignProc(@mysql_num_fields, 'mysql_num_fields'); + AssignProc(@mysql_num_rows, 'mysql_num_rows'); + AssignProc(@mysql_ping, 'mysql_ping'); + AssignProc(@mysql_options, 'mysql_options'); + AssignProc(@mysql_optionsv, 'mysql_optionsv', False); + AssignProc(@mysql_real_connect, 'mysql_real_connect'); + AssignProc(@mysql_real_query, 'mysql_real_query'); + AssignProc(@mysql_stat, 'mysql_stat'); + AssignProc(@mysql_store_result, 'mysql_store_result'); + AssignProc(@mysql_thread_id, 'mysql_thread_id'); + AssignProc(@mysql_next_result, 'mysql_next_result'); + AssignProc(@mysql_set_character_set, 'mysql_set_character_set'); + AssignProc(@mysql_thread_init, 'mysql_thread_init'); + AssignProc(@mysql_thread_end, 'mysql_thread_end'); + AssignProc(@mysql_warning_count, 'mysql_warning_count'); +end; + + +initialization + +// Keywords copied from SynHighligherSQL +MySQLKeywords := TStringList.Create; +MySQLKeywords.CommaText := 'ACCESSIBLE,ACCOUNT,ACTION,ACTIVE,ADD,ADMIN,AFTER,AGAINST,AGGREGATE,' + + 'ALGORITHM,ALL,ALTER,ALWAYS,ANALYSE,ANALYZE,AND,ANY,ARRAY,AS,ASC,' + + 'ASENSITIVE,AT,ATTRIBUTE,AUTHENTICATION,AUTOEXTEND_SIZE,AUTO_INCREMENT,' + + 'AVG_ROW_LENGTH,BACKUP,BEFORE,BEGIN,BETWEEN,BINLOG,BIT,BLOCK,BOTH,BUCKETS,' + + 'BULK,BY,CACHE,CALL,CASCADE,CASCADED,CATALOG_NAME,CHAIN,CHALLENGE_RESPONSE,' + + 'CHANGE,CHANGED,CHANNEL,CHARACTER,CHARSET,CHECK,CHECKSUM,CIPHER,' + + 'CLASS_ORIGIN,CLIENT,CLONE,CODE,COLLATE,COLLATION,COLUMN,COLUMNS,' + + 'COLUMN_FORMAT,COLUMN_NAME,COMMENT,COMMIT,COMMITTED,COMPLETION,COMPONENT,' + + 'COMPRESSION,CONCURRENT,CONDITION,CONNECTION,CONSISTENT,CONSTRAINT,' + + 'CONSTRAINT_CATALOG,CONSTRAINT_NAME,CONSTRAINT_SCHEMA,CONTAINS,CONTEXT,' + + 'CONTINUE,CONVERT,CPU,CREATE,CROSS,CUBE,CUME_DIST,CURRENT,CURSOR,' + + 'CURSOR_NAME,DATA,DATABASE,DATABASES,DATAFILE,DAY_HOUR,DAY_MICROSECOND,' + + 'DAY_MINUTE,DAY_SECOND,DEALLOCATE,DEC,DECLARE,DEFAULT,DEFAULT_AUTH,DEFINER,' + + 'DEFINITION,DELAYED,DELAY_KEY_WRITE,DELETE,DENSE_RANK,DESC,DESCRIBE,' + + 'DESCRIPTION,DES_KEY_FILE,DETERMINISTIC,DIAGNOSTICS,DIRECTORY,DISABLE,' + + 'DISCARD,DISTINCT,DISTINCTROW,DIV,DO,DROP,DUAL,DUMPFILE,DUPLICATE,EACH,' + + 'ELSE,ELSEIF,EMPTY,ENABLE,ENCLOSED,ENCRYPTION,END,ENDS,ENFORCED,ENGINE,' + + 'ENGINES,ENGINE_ATTRIBUTE,ERROR,ERRORS,ESCAPE,ESCAPED,EVENT,EVENTS,EVERY,' + + 'EXCEPT,EXCHANGE,EXCLUDE,EXECUTE,EXISTS,EXPANSION,EXPIRE,EXPLAIN,EXPORT,' + + 'EXTENDED,EXTENT_SIZE,FACTOR,FAILED_LOGIN_ATTEMPTS,FALSE,FAST,FAULTS,' + + 'FIELDS,FILE,FILE_BLOCK_SIZE,FILTER,FINISH,FIRST,FIRST_VALUE,FLOAT4,FLOAT8,' + + 'FLUSH,FOLLOWING,FOLLOWS,FOR,FORCE,FOREIGN,FOUND,FROM,FULL,FULLTEXT,' + + 'FUNCTION,GENERAL,GENERATE,GENERATED,GEOMCOLLECTION,GET,' + + 'GET_MASTER_PUBLIC_KEY,GET_SOURCE_PUBLIC_KEY,GLOBAL,GRANT,GRANTS,GROUP,' + + 'GROUPING,GROUPS,GROUP_REPLICATION,GTID_ONLY,HAVING,HELP,HIGH_PRIORITY,' + + 'HISTOGRAM,HISTORY,HOST,HOSTS,HOUR_MICROSECOND,HOUR_MINUTE,HOUR_SECOND,' + + 'IDENTIFIED,IGNORE,IGNORE_SERVER_IDS,IMPORT,IN,INACTIVE,INDEX,INDEXES,' + + 'INFILE,INITIAL,INITIAL_SIZE,INITIATE,INNER,INOUT,INSENSITIVE,INSERT,' + + 'INSERT_METHOD,INSTALL,INSTANCE,INT1,INT2,INT3,INT4,INT8,INTERSECT,INTO,' + + 'INVISIBLE,INVOKER,IO,IO_AFTER_GTIDS,IO_BEFORE_GTIDS,IO_THREAD,IPC,IS,' + + 'ISOLATION,ISSUER,JOIN,JSON,JSON_TABLE,JSON_VALUE,KEY,KEYRING,KEYS,' + + 'KEY_BLOCK_SIZE,KILL,LAG,LANGUAGE,LAST,LAST_VALUE,LATERAL,LEAD,LEADING,' + + 'LEAVES,LESS,LEVEL,LIKE,LIMIT,LINEAR,LINES,LIST,LOAD,LOCAL,LOCK,LOCKED,' + + 'LOCKS,LOGFILE,LOGS,LONG,LOW_PRIORITY,MASTER,MASTER_AUTO_POSITION,' + + 'MASTER_BIND,MASTER_COMPRESSION_ALGORITHMS,MASTER_CONNECT_RETRY,' + + 'MASTER_DELAY,MASTER_HEARTBEAT_PERIOD,MASTER_HOST,MASTER_LOG_FILE,' + + 'MASTER_LOG_POS,MASTER_PASSWORD,MASTER_PORT,MASTER_PUBLIC_KEY_PATH,' + + 'MASTER_RETRY_COUNT,MASTER_SERVER_ID,MASTER_SSL,MASTER_SSL_CA,' + + 'MASTER_SSL_CAPATH,MASTER_SSL_CERT,MASTER_SSL_CIPHER,MASTER_SSL_CRL,' + + 'MASTER_SSL_CRLPATH,MASTER_SSL_KEY,MASTER_SSL_VERIFY_SERVER_CERT,' + + 'MASTER_TLS_CIPHERSUITES,MASTER_TLS_VERSION,MASTER_USER,' + + 'MASTER_ZSTD_COMPRESSION_LEVEL,MATCH,MAXVALUE,MAX_CONNECTIONS_PER_HOUR,' + + 'MAX_QUERIES_PER_HOUR,MAX_ROWS,MAX_SIZE,MAX_UPDATES_PER_HOUR,' + + 'MAX_USER_CONNECTIONS,MEDIUM,MEMBER,MESSAGE_TEXT,MIDDLEINT,MIGRATE,' + + 'MINUTE_MICROSECOND,MINUTE_SECOND,MIN_ROWS,MOD,MODE,MODIFIES,MODIFY,MUTEX,' + + 'MYSQL_ERRNO,NAME,NAMES,NATURAL,NCHAR,NESTED,NETWORK_NAMESPACE,NEVER,NEW,' + + 'NEXT,NO,NODEGROUP,NONE,NOT,NOWAIT,NO_WAIT,NO_WRITE_TO_BINLOG,NTH_VALUE,' + + 'NTILE,NULL,NULLS,NUMBER,NVARCHAR,OF,OFF,OFFSET,OJ,OLD,ON,ONE,ONLY,OPEN,' + + 'OPTIMIZE,OPTIMIZER_COSTS,OPTION,OPTIONAL,OPTIONALLY,OPTIONS,OR,ORDER,' + + 'ORDINALITY,ORGANIZATION,OTHERS,OUT,OUTER,OUTFILE,OVER,OWNER,PACK_KEYS,' + + 'PAGE,PARSER,PARSE_GCOL_EXPR,PARTIAL,PARTITION,PARTITIONING,PARTITIONS,' + + 'PASSWORD_LOCK_TIME,PATH,PERCENT_RANK,PERSIST,PERSIST_ONLY,PHASE,PLUGIN,' + + 'PLUGINS,PLUGIN_DIR,PORT,PRECEDES,PRECEDING,PREPARE,PRESERVE,PREV,PRIMARY,' + + 'PRIVILEGES,PRIVILEGE_CHECKS_USER,PROCEDURE,PROCESS,PROCESSLIST,PROFILE,' + + 'PROFILES,PROXY,PURGE,QUERY,QUICK,RANDOM,RANGE,RANK,READ,READS,READ_ONLY,' + + 'READ_WRITE,REBUILD,RECOVER,RECURSIVE,REDOFILE,REDO_BUFFER_SIZE,REFERENCE,' + + 'REFERENCES,REGEXP,REGISTRATION,RELAY,RELAYLOG,RELAY_LOG_FILE,' + + 'RELAY_LOG_POS,RELAY_THREAD,RELEASE,RELOAD,REMOTE,REMOVE,RENAME,REORGANIZE,' + + 'REPAIR,REPEATABLE,REPLACE,REPLICA,REPLICAS,REPLICATE_DO_DB,' + + 'REPLICATE_DO_TABLE,REPLICATE_IGNORE_DB,REPLICATE_IGNORE_TABLE,' + + 'REPLICATE_REWRITE_DB,REPLICATE_WILD_DO_TABLE,REPLICATE_WILD_IGNORE_TABLE,' + + 'REPLICATION,REQUIRE,REQUIRE_ROW_FORMAT,RESET,RESIGNAL,RESOURCE,RESPECT,' + + 'RESTART,RESTORE,RESTRICT,RESUME,RETAIN,RETURN,RETURNED_SQLSTATE,RETURNING,' + + 'RETURNS,REUSE,REVOKE,RLIKE,ROLE,ROLLBACK,ROLLUP,ROTATE,ROUTINE,ROW,ROWS,' + + 'ROW_FORMAT,ROW_NUMBER,RTREE,SAVEPOINT,SCHEDULE,SCHEMA,SCHEMAS,SCHEMA_NAME,' + + 'SECONDARY,SECONDARY_ENGINE,SECONDARY_ENGINE_ATTRIBUTE,SECONDARY_LOAD,' + + 'SECONDARY_UNLOAD,SECOND_MICROSECOND,SECURITY,SELECT,SENSITIVE,SEPARATOR,' + + 'SERIALIZABLE,SERVER,SESSION,SET,SHARE,SHOW,SHUTDOWN,SIGNAL,SIMPLE,SKIP,' + + 'SLAVE,SLOW,SNAPSHOT,SOCKET,SOME,SONAME,SOUNDS,SOURCE,SOURCE_AUTO_POSITION,' + + 'SOURCE_BIND,SOURCE_COMPRESSION_ALGORITHMS,SOURCE_CONNECT_RETRY,' + + 'SOURCE_DELAY,SOURCE_HEARTBEAT_PERIOD,SOURCE_HOST,SOURCE_LOG_FILE,' + + 'SOURCE_LOG_POS,SOURCE_PASSWORD,SOURCE_PORT,SOURCE_PUBLIC_KEY_PATH,' + + 'SOURCE_RETRY_COUNT,SOURCE_SSL,SOURCE_SSL_CA,SOURCE_SSL_CAPATH,' + + 'SOURCE_SSL_CERT,SOURCE_SSL_CIPHER,SOURCE_SSL_CRL,SOURCE_SSL_CRLPATH,' + + 'SOURCE_SSL_KEY,SOURCE_SSL_VERIFY_SERVER_CERT,SOURCE_TLS_CIPHERSUITES,' + + 'SOURCE_TLS_VERSION,SOURCE_USER,SOURCE_ZSTD_COMPRESSION_LEVEL,SPATIAL,' + + 'SPECIFIC,SQL,SQLEXCEPTION,SQLSTATE,SQLWARNING,SQL_AFTER_GTIDS,' + + 'SQL_AFTER_MTS_GAPS,SQL_BEFORE_GTIDS,SQL_BIG_RESULT,SQL_BUFFER_RESULT,' + + 'SQL_CACHE,SQL_CALC_FOUND_ROWS,SQL_NO_CACHE,SQL_SMALL_RESULT,SQL_THREAD,' + + 'SQL_TSI_DAY,SQL_TSI_HOUR,SQL_TSI_MINUTE,SQL_TSI_MONTH,SQL_TSI_QUARTER,' + + 'SQL_TSI_SECOND,SQL_TSI_WEEK,SQL_TSI_YEAR,SSL,STACKED,START,STARTING,' + + 'STARTS,STATS_AUTO_RECALC,STATS_PERSISTENT,STATS_SAMPLE_PAGES,STATUS,STOP,' + + 'STORAGE,STORED,STRAIGHT_JOIN,STREAM,SUBCLASS_ORIGIN,SUBJECT,SUBPARTITION,' + + 'SUBPARTITIONS,SUPER,SUSPEND,SWAPS,SWITCHES,SYSTEM,TABLE,TABLES,TABLESPACE,' + + 'TABLE_CHECKSUM,TABLE_NAME,TEMPORARY,TERMINATED,THAN,THREAD_PRIORITY,TIES,' + + 'TLS,TO,TRAILING,TRANSACTION,TRIGGER,TRIGGERS,TRUE,TYPE,TYPES,UNBOUNDED,' + + 'UNCOMMITTED,UNDO,UNDOFILE,UNDO_BUFFER_SIZE,UNINSTALL,UNION,UNIQUE,UNKNOWN,' + + 'UNLOCK,UNREGISTER,UPDATE,UPGRADE,URL,USAGE,USE,USER_RESOURCES,USE_FRM,' + + 'USING,VALIDATION,VALUE,VALUES,VARCHARACTER,VARIABLES,VARYING,VCPU,VIEW,' + + 'VIRTUAL,VISIBLE,WAIT,WARNINGS,WHERE,WINDOW,WITH,WITHOUT,WORK,WRAPPER,' + + 'WRITE,X509,XA,XID,XML,XOR,YEAR_MONTH,ZONE,' + // SQL Plus commands: + + 'CLOSE,CONDITION,CONTINUE,CURSOR,DECLARE,DO,EXIT,FETCH,FOUND,GOTO,' + + 'HANDLER,ITERATE,LANGUAGE,LEAVE,LOOP,UNTIL,WHILE'; + +// Error codes copied from perror.exe +MySQLErrorCodes := Explode(',', '0=No error,'+ + '1=Operation not permitted,'+ + '2=No such file or directory,'+ + '3=No such process,'+ + '4=Interrupted function call,'+ + '5=Input/output error,'+ + '6=No such device or address,'+ + '7=Arg list too long,'+ + '8=Exec format error,'+ + '9=Bad file descriptor,'+ + '10=No child processes,'+ + '11=Resource temporarily unavailable,'+ + '12=Not enough space,'+ + '13=Permission denied,'+ + '14=Bad address,'+ + '16=Resource device,'+ + '17=File exists,'+ + '18=Improper link,'+ + '19=No such device,'+ + '20=Not a directory,'+ + '21=Is a directory,'+ + '22=Invalid argument,'+ + '23=Too many open files in system,'+ + '24=Too many open files,'+ + '25=Inappropriate I/O control operation,'+ + '27=File too large,'+ + '28=No space left on device,'+ + '29=Invalid seek,'+ + '30=Read-only file system,'+ + '31=Too many links,'+ + '32=Broken pipe,'+ + '33=Domain error,'+ + '34=Result too large,'+ + '36=Resource deadlock avoided,'+ + '38=Filename too long,'+ + '39=No locks available,'+ + '40=Function not implemented,'+ + '41=Directory not empty,'+ + '42=Illegal byte sequence,'+ + '120=Didn''t find key on read or update,'+ + '121=Duplicate key on write or update,'+ + '123=Someone has changed the row since it was read (while the table was locked to prevent it),'+ + '124=Wrong index given to function,'+ + '126=Index file is crashed,'+ + '127=Record-file is crashed,'+ + '128=Out of memory,'+ + '130=Incorrect file format,'+ + '131=Command not supported by database,'+ + '132=Old database file,'+ + '133=No record read before update,'+ + '134=Record was already deleted (or record file crashed),'+ + '135=No more room in record file,'+ + '136=No more room in index file,'+ + '137=No more records (read after end of file),'+ + '138=Unsupported extension used for table,'+ + '139=Too big row,'+ + '140=Wrong create options,'+ + '141=Duplicate unique key or constraint on write or update,'+ + '142=Unknown character set used,'+ + '143=Conflicting table definitions in sub-tables of MERGE table,'+ + '144=Table is crashed and last repair failed,'+ + '145=Table was marked as crashed and should be repaired,'+ + '146=Lock timed out; Retry transaction,'+ + '147=Lock table is full; Restart program with a larger locktable,'+ + '148=Updates are not allowed under a read only transactions,'+ + '149=Lock deadlock; Retry transaction,'+ + '150=Foreign key constraint is incorrectly formed,'+ + '151=Cannot add a child row,'+ + '152=Cannot delete a parent row'); + + + +end. diff --git a/source/dbstructures.pas b/source/dbstructures.pas new file mode 100644 index 00000000..3217bd22 --- /dev/null +++ b/source/dbstructures.pas @@ -0,0 +1,218 @@ +unit dbstructures; + +{$mode delphi}{$H+} + +interface + +uses + Classes, SysUtils, Graphics; + + +type + + // Column types + TDBDatatypeIndex = (dbdtTinyint, dbdtSmallint, dbdtMediumint, dbdtInt, dbdtUint, dbdtBigint, dbdtSerial, dbdtBigSerial, + dbdtFloat, dbdtDouble, dbdtDecimal, dbdtNumeric, dbdtReal, dbdtDoublePrecision, dbdtMoney, dbdtSmallmoney, + dbdtDate, dbdtTime, dbdtYear, dbdtDatetime, dbdtDatetime2, dbdtDatetimeOffset, dbdtSmalldatetime, dbdtTimestamp, dbdtInterval, + dbdtChar, dbdtNchar, dbdtVarchar, dbdtNvarchar, dbdtTinytext, dbdtText, dbdtCiText, dbdtNtext, dbdtMediumtext, dbdtLongtext, + dbdtJson, dbdtJsonB, dbdtCidr, dbdtInet, dbdtMacaddr, + dbdtBinary, dbdtVarbinary, dbdtTinyblob, dbdtBlob, dbdtMediumblob, dbdtLongblob, dbdtVector, dbdtImage, + dbdtEnum, dbdtSet, dbdtBit, dbdtVarBit, dbdtBool, dbdtRegClass, dbdtRegProc, dbdtUnknown, + dbdtCursor, dbdtSqlvariant, dbdtTable, dbdtUniqueidentifier, dbdtInet4, dbdtInet6, dbdtHierarchyid, dbdtXML, + dbdtPoint, dbdtLinestring, dbdtLineSegment, dbdtPolygon, dbdtGeometry, dbdtBox, dbdtPath, dbdtCircle, dbdtMultipoint, dbdtMultilinestring, dbdtMultipolygon, dbdtGeometrycollection + ); + + // Column type categorization + TDBDatatypeCategoryIndex = (dtcInteger, dtcReal, dtcText, dtcBinary, dtcTemporal, dtcSpatial, dtcOther); + + // Column type structure + TDBDatatype = record + Index: TDBDatatypeIndex; + NativeType: Integer; // MySQL column type constant (e.g. 1 = TINYINT). See include/mysql.h.pp. + NativeTypes: String; // Same as above, but for multiple ids (e.g. PostgreSQL oids). Prefer over NativeType. See GetDatatypeByNativeType. + Name: String; + Names: String; + Description: String; + HasLength: Boolean; // Can have Length- or Set-attribute? + RequiresLength: Boolean; // Must have a Length- or Set-attribute? + MaxSize: Int64; + DefaultSize: Int64; // TEXT and BLOB allow custom length, but we want to leave the default max length away from ALTER TABLE's + HasBinary: Boolean; // Can be binary? + HasDefault: Boolean; // Can have a default value? + LoadPart: Boolean; // Select per SUBSTR() or LEFT() + DefLengthSet: String; // Should be set for types which require a length/set + Format: String; // Used for date/time values when displaying and generating queries + ValueMustMatch: String; + Category: TDBDatatypeCategoryIndex; + MinVersion: Integer; + end; + + // Column type category structure + TDBDatatypeCategory = record + Index: TDBDatatypeCategoryIndex; + Name: String; + Color: TColor; + NullColor: TColor; + end; + + // Server variables + TVarScope = (vsGlobal, vsSession, vsBoth); + TServerVariable = record + Name: String; + IsDynamic: Boolean; + VarScope: TVarScope; + EnumValues: String; + end; + + // Custom exception class for any connection or database related error + EDbError = class(Exception) + private + FErrorCode: Cardinal; + FHint: String; + public + property ErrorCode: Cardinal read FErrorCode; + property Hint: String read FHint; + constructor Create(const Msg: string; const ErrorCode_: Cardinal=0; const Hint_: String=''); + end; + + // DLL loading + TDbLib = class(TObject) + const + LIB_PROC_ERROR: Cardinal = 1000; + private + FHandle: TLibHandle; + protected + FDllFile: String; + procedure AssignProc(var Proc: Pointer; Name: PAnsiChar; Mandantory: Boolean=True); + procedure AssignProcedures; virtual; abstract; + public + property Handle: TLibHandle read FHandle; + property DllFile: String read FDllFile; + constructor Create(DllFile_, DefaultDll: String); virtual; + destructor Destroy; override; + end; + + +var + + // Column type categories + DatatypeCategories: array[TDBDatatypeCategoryIndex] of TDBDatatypeCategory = ( + ( + Index: dtcInteger; + Name: 'Integer' + ), + ( + Index: dtcReal; + Name: 'Real' + ), + ( + Index: dtcText; + Name: 'Text' + ), + ( + Index: dtcBinary; + Name: 'Binary' + ), + ( + Index: dtcTemporal; + Name: 'Temporal (time)' + ), + ( + Index: dtcSpatial; + Name: 'Spatial (geometry)' + ), + ( + Index: dtcOther; + Name: 'Other' + ) + ); + + + +implementation + +uses apphelpers; + + + +{ EDbError } + +constructor EDbError.Create(const Msg: string; const ErrorCode_: Cardinal=0; const Hint_: String=''); +begin + FErrorCode := ErrorCode_; + FHint := Hint_; + inherited Create(Msg); +end; + + + +{ TDbLib } + +constructor TDbLib.Create(DllFile_, DefaultDll: String); +var + msg, ErrorHint: String; +begin + // Load DLL as is (with or without path) + inherited Create; + FDllFile := DllFile_; + if not FileExists(FDllFile) then begin + msg := f_('File does not exist: %s', [FDllFile]) + + sLineBreak + sLineBreak + + f_('Please launch %s from the directory where you have installed it. Or just reinstall %s.', [APPNAME, APPNAME] + ); + Raise EdbError.Create(msg); + end; + + FHandle := LoadLibrary(FDllFile); + if FHandle = NilHandle then begin + msg := f_('Library %s could not be loaded. Please select a different one.', + [ExtractFileName(FDllFile)] + ); + if GetLastOSError <> 0 then begin + msg := msg + sLineBreak + sLineBreak + f_('Internal error %d: %s', [GetLastOSError, SysErrorMessage(GetLastOSError)]); + end; + if (DefaultDll <> '') and (ExtractFileName(FDllFile) <> DefaultDll) then begin + ErrorHint := f_('You could try the default library %s in your session settings. (Current: %s)', + [DefaultDll, ExtractFileName(FDllFile)] + ); + end else begin + ErrorHint := ''; + end; + Raise EDbError.Create(msg, GetLastOSError, ErrorHint); + end; + + // Dll was loaded, now initialize required procedures + AssignProcedures; +end; + + +destructor TDbLib.Destroy; +begin + if FHandle <> 0 then begin + FreeLibrary(FHandle); + FHandle := 0; + end; + inherited; +end; + + +procedure TDbLib.AssignProc(var Proc: Pointer; Name: PAnsiChar; Mandantory: Boolean=True); +var + msg: String; +begin + // Map library procedure to internal procedure + Proc := GetProcAddress(FHandle, Name); + if Proc = nil then begin + if Mandantory then begin + msg := f_('Library error in %s: Could not find procedure address for "%s"', + [ExtractFileName(FDllFile), Name] + ); + if GetLastOSError <> 0 then + msg := msg + sLineBreak + sLineBreak + f_('Internal error %d: %s', [GetLastOSError, SysErrorMessage(GetLastOSError)]); + Raise EDbError.Create(msg, LIB_PROC_ERROR); + end; + end; +end; + + +end. diff --git a/source/main.lfm b/source/main.lfm new file mode 100644 index 00000000..6937d110 --- /dev/null +++ b/source/main.lfm @@ -0,0 +1,1171 @@ +object MainForm: TMainForm + Left = 585 + Height = 500 + Top = 289 + Width = 998 + Caption = 'MainForm' + ClientHeight = 500 + ClientWidth = 998 + DesignTimePPI = 120 + Menu = MainMenu1 + OnCreate = FormCreate + Position = poMainFormCenter + LCLVersion = '3.8.0.0' + object StatusBar: TStatusBar + Left = 0 + Height = 29 + Top = 471 + Width = 998 + Panels = < + item + Width = 150 + end + item + Width = 110 + end + item + Style = psOwnerDraw + Width = 140 + end + item + Style = psOwnerDraw + Width = 170 + end + item + Width = 170 + end + item + Style = psOwnerDraw + Width = 170 + end + item + Style = psOwnerDraw + Width = 250 + end> + SimplePanel = False + end + object ToolBarMainButtons: TToolBar + Left = 0 + Height = 30 + Top = 0 + Width = 998 + AutoSize = True + Caption = 'ToolBarMainButtons' + Images = ImageListIcons8 + TabOrder = 1 + object ToolButton1: TToolButton + Left = 1 + Top = 2 + Action = actSessionManager + Style = tbsDropDown + end + end + inline SynMemoSQLLog: TSynEdit + Left = 0 + Height = 80 + Top = 391 + Width = 998 + Align = alBottom + Font.Height = -13 + Font.Name = 'Courier New' + Font.Pitch = fpFixed + Font.Quality = fqCleartypeNatural + ParentColor = False + ParentFont = False + TabOrder = 2 + Gutter.Width = 68 + Gutter.MouseActions = <> + RightGutter.Width = 0 + RightGutter.MouseActions = <> + Highlighter = SynSQLSynUsed + Keystrokes = < + item + Command = ecUp + ShortCut = 38 + end + item + Command = ecSelUp + ShortCut = 8230 + end + item + Command = ecScrollUp + ShortCut = 16422 + end + item + Command = ecDown + ShortCut = 40 + end + item + Command = ecSelDown + ShortCut = 8232 + end + item + Command = ecScrollDown + ShortCut = 16424 + end + item + Command = ecLeft + ShortCut = 37 + end + item + Command = ecSelLeft + ShortCut = 8229 + end + item + Command = ecWordLeft + ShortCut = 16421 + end + item + Command = ecSelWordLeft + ShortCut = 24613 + end + item + Command = ecRight + ShortCut = 39 + end + item + Command = ecSelRight + ShortCut = 8231 + end + item + Command = ecWordRight + ShortCut = 16423 + end + item + Command = ecSelWordRight + ShortCut = 24615 + end + item + Command = ecPageDown + ShortCut = 34 + end + item + Command = ecSelPageDown + ShortCut = 8226 + end + item + Command = ecPageBottom + ShortCut = 16418 + end + item + Command = ecSelPageBottom + ShortCut = 24610 + end + item + Command = ecPageUp + ShortCut = 33 + end + item + Command = ecSelPageUp + ShortCut = 8225 + end + item + Command = ecPageTop + ShortCut = 16417 + end + item + Command = ecSelPageTop + ShortCut = 24609 + end + item + Command = ecLineStart + ShortCut = 36 + end + item + Command = ecSelLineStart + ShortCut = 8228 + end + item + Command = ecEditorTop + ShortCut = 16420 + end + item + Command = ecSelEditorTop + ShortCut = 24612 + end + item + Command = ecLineEnd + ShortCut = 35 + end + item + Command = ecSelLineEnd + ShortCut = 8227 + end + item + Command = ecEditorBottom + ShortCut = 16419 + end + item + Command = ecSelEditorBottom + ShortCut = 24611 + end + item + Command = ecToggleMode + ShortCut = 45 + end + item + Command = ecCopy + ShortCut = 16429 + end + item + Command = ecPaste + ShortCut = 8237 + end + item + Command = ecDeleteChar + ShortCut = 46 + end + item + Command = ecCut + ShortCut = 8238 + end + item + Command = ecDeleteLastChar + ShortCut = 8 + end + item + Command = ecDeleteLastChar + ShortCut = 8200 + end + item + Command = ecDeleteLastWord + ShortCut = 16392 + end + item + Command = ecUndo + ShortCut = 32776 + end + item + Command = ecRedo + ShortCut = 40968 + end + item + Command = ecLineBreak + ShortCut = 13 + end + item + Command = ecSelectAll + ShortCut = 16449 + end + item + Command = ecCopy + ShortCut = 16451 + end + item + Command = ecBlockIndent + ShortCut = 24649 + end + item + Command = ecLineBreak + ShortCut = 16461 + end + item + Command = ecInsertLine + ShortCut = 16462 + end + item + Command = ecDeleteWord + ShortCut = 16468 + end + item + Command = ecBlockUnindent + ShortCut = 24661 + end + item + Command = ecPaste + ShortCut = 16470 + end + item + Command = ecCut + ShortCut = 16472 + end + item + Command = ecDeleteLine + ShortCut = 16473 + end + item + Command = ecDeleteEOL + ShortCut = 24665 + end + item + Command = ecUndo + ShortCut = 16474 + end + item + Command = ecRedo + ShortCut = 24666 + end + item + Command = ecGotoMarker0 + ShortCut = 16432 + end + item + Command = ecGotoMarker1 + ShortCut = 16433 + end + item + Command = ecGotoMarker2 + ShortCut = 16434 + end + item + Command = ecGotoMarker3 + ShortCut = 16435 + end + item + Command = ecGotoMarker4 + ShortCut = 16436 + end + item + Command = ecGotoMarker5 + ShortCut = 16437 + end + item + Command = ecGotoMarker6 + ShortCut = 16438 + end + item + Command = ecGotoMarker7 + ShortCut = 16439 + end + item + Command = ecGotoMarker8 + ShortCut = 16440 + end + item + Command = ecGotoMarker9 + ShortCut = 16441 + end + item + Command = ecSetMarker0 + ShortCut = 24624 + end + item + Command = ecSetMarker1 + ShortCut = 24625 + end + item + Command = ecSetMarker2 + ShortCut = 24626 + end + item + Command = ecSetMarker3 + ShortCut = 24627 + end + item + Command = ecSetMarker4 + ShortCut = 24628 + end + item + Command = ecSetMarker5 + ShortCut = 24629 + end + item + Command = ecSetMarker6 + ShortCut = 24630 + end + item + Command = ecSetMarker7 + ShortCut = 24631 + end + item + Command = ecSetMarker8 + ShortCut = 24632 + end + item + Command = ecSetMarker9 + ShortCut = 24633 + end + item + Command = EcFoldLevel1 + ShortCut = 41009 + end + item + Command = EcFoldLevel2 + ShortCut = 41010 + end + item + Command = EcFoldLevel3 + ShortCut = 41011 + end + item + Command = EcFoldLevel4 + ShortCut = 41012 + end + item + Command = EcFoldLevel5 + ShortCut = 41013 + end + item + Command = EcFoldLevel6 + ShortCut = 41014 + end + item + Command = EcFoldLevel7 + ShortCut = 41015 + end + item + Command = EcFoldLevel8 + ShortCut = 41016 + end + item + Command = EcFoldLevel9 + ShortCut = 41017 + end + item + Command = EcFoldLevel0 + ShortCut = 41008 + end + item + Command = EcFoldCurrent + ShortCut = 41005 + end + item + Command = EcUnFoldCurrent + ShortCut = 41003 + end + item + Command = EcToggleMarkupWord + ShortCut = 32845 + end + item + Command = ecNormalSelect + ShortCut = 24654 + end + item + Command = ecColumnSelect + ShortCut = 24643 + end + item + Command = ecLineSelect + ShortCut = 24652 + end + item + Command = ecTab + ShortCut = 9 + end + item + Command = ecShiftTab + ShortCut = 8201 + end + item + Command = ecMatchBracket + ShortCut = 24642 + end + item + Command = ecColSelUp + ShortCut = 40998 + end + item + Command = ecColSelDown + ShortCut = 41000 + end + item + Command = ecColSelLeft + ShortCut = 40997 + end + item + Command = ecColSelRight + ShortCut = 40999 + end + item + Command = ecColSelPageDown + ShortCut = 40994 + end + item + Command = ecColSelPageBottom + ShortCut = 57378 + end + item + Command = ecColSelPageUp + ShortCut = 40993 + end + item + Command = ecColSelPageTop + ShortCut = 57377 + end + item + Command = ecColSelLineStart + ShortCut = 40996 + end + item + Command = ecColSelLineEnd + ShortCut = 40995 + end + item + Command = ecColSelEditorTop + ShortCut = 57380 + end + item + Command = ecColSelEditorBottom + ShortCut = 57379 + end> + MouseActions = <> + MouseTextActions = <> + MouseSelActions = <> + Options = [eoAutoIndent, eoBracketHighlight, eoGroupUndo, eoSmartTabs, eoTabsToSpaces, eoTrimTrailingSpaces] + VisibleSpecialChars = [vscSpace, vscTabAtLast] + ReadOnly = True + RightEdge = 0 + ScrollBars = ssVertical + SelectedColor.BackPriority = 50 + SelectedColor.ForePriority = 50 + SelectedColor.FramePriority = 50 + SelectedColor.BoldPriority = 50 + SelectedColor.ItalicPriority = 50 + SelectedColor.UnderlinePriority = 50 + SelectedColor.StrikeOutPriority = 50 + BracketHighlightStyle = sbhsBoth + BracketMatchColor.Background = clNone + BracketMatchColor.Foreground = clNone + BracketMatchColor.Style = [fsBold] + FoldedCodeColor.Background = clNone + FoldedCodeColor.Foreground = clGray + FoldedCodeColor.FrameColor = clGray + MouseLinkColor.Background = clNone + MouseLinkColor.Foreground = clBlue + LineHighlightColor.Background = clNone + LineHighlightColor.Foreground = clNone + inline SynLeftGutterPartList1: TSynGutterPartList + object SynGutterMarks1: TSynGutterMarks + Width = 30 + MouseActions = <> + end + object SynGutterLineNumber1: TSynGutterLineNumber + Width = 17 + MouseActions = <> + MarkupInfo.Background = clBtnFace + MarkupInfo.Foreground = clNone + DigitCount = 2 + ShowOnlyLineNumbersMultiplesOf = 1 + ZeroStart = False + LeadingZeros = False + end + object SynGutterChanges1: TSynGutterChanges + Width = 5 + MouseActions = <> + ModifiedColor = 59900 + SavedColor = clGreen + end + object SynGutterSeparator1: TSynGutterSeparator + Width = 3 + MouseActions = <> + MarkupInfo.Background = clWhite + MarkupInfo.Foreground = clGray + end + object SynGutterCodeFolding1: TSynGutterCodeFolding + Width = 13 + MouseActions = <> + MarkupInfo.Background = clNone + MarkupInfo.Foreground = clGray + MouseActionsExpanded = <> + MouseActionsCollapsed = <> + end + end + end + object spltTopBottom: TSplitter + Cursor = crVSplit + Left = 0 + Height = 6 + Top = 385 + Width = 998 + Align = alBottom + AutoSnap = False + ResizeAnchor = akBottom + end + object Panel1: TPanel + Left = 0 + Height = 355 + Top = 30 + Width = 998 + Align = alClient + AutoSize = True + BevelOuter = bvNone + ClientHeight = 355 + ClientWidth = 998 + TabOrder = 4 + object pnlLeft: TPanel + Left = 0 + Height = 355 + Top = 0 + Width = 212 + Align = alLeft + BevelOuter = bvNone + ClientHeight = 355 + ClientWidth = 212 + TabOrder = 0 + object pnlPreview: TPanel + Left = 0 + Height = 100 + Top = 255 + Width = 212 + Align = alBottom + BevelOuter = bvNone + TabOrder = 0 + Visible = False + end + object spltPreview: TSplitter + Cursor = crVSplit + Left = 0 + Height = 6 + Top = 249 + Width = 212 + Align = alBottom + ResizeAnchor = akBottom + Visible = False + end + object ToolBarTree: TToolBar + Left = 0 + Top = 0 + Width = 212 + TabOrder = 2 + end + object DBtree: TLazVirtualStringTree + Left = 0 + Height = 217 + Top = 32 + Width = 212 + Align = alClient + DefaultText = 'Node' + Header.AutoSizeIndex = 0 + Header.Columns = <> + Header.MainColumn = -1 + TabOrder = 3 + end + end + object spltDBtree: TSplitter + Left = 212 + Height = 355 + Top = 0 + Width = 6 + end + object Panel2: TPanel + Left = 218 + Height = 355 + Top = 0 + Width = 780 + Align = alClient + BevelOuter = bvNone + ClientHeight = 355 + ClientWidth = 780 + TabOrder = 2 + object pnlFilterVT: TPanel + Left = 0 + Height = 39 + Top = 316 + Width = 780 + Align = alBottom + BevelOuter = bvNone + TabOrder = 0 + end + object PageControlMain: TPageControl + Left = 0 + Height = 316 + Top = 0 + Width = 780 + ActivePage = tabHost + Align = alClient + Images = ImageListIcons8 + TabIndex = 0 + TabOrder = 1 + object tabHost: TTabSheet + Caption = 'Host' + ImageIndex = 1 + end + object tabDatabase: TTabSheet + Caption = 'Database' + end + object tabTable: TTabSheet + Caption = 'Table' + end + object tabData: TTabSheet + Caption = 'Data' + end + object tabQuery: TTabSheet + Caption = 'Query' + end + end + end + end + object MainMenu1: TMainMenu + Images = ImageListIcons8 + Left = 48 + Top = 33 + object MenuItem1: TMenuItem + Caption = 'File' + object MenuItem8: TMenuItem + Action = actExitApplication + end + end + object MenuItem2: TMenuItem + Caption = 'Edit' + end + object MenuItem3: TMenuItem + Caption = 'Search' + end + object MenuItem4: TMenuItem + Caption = 'Query' + end + object MenuItem5: TMenuItem + Caption = 'Tools' + end + object MenuItem6: TMenuItem + Caption = 'Go to' + end + object MenuItem7: TMenuItem + Caption = 'Help' + end + end + object ActionList1: TActionList + Images = ImageListIcons8 + Left = 48 + Top = 120 + object actSessionManager: TAction + Category = 'File' + Caption = 'Session manager' + Hint = 'Display session manager' + ImageIndex = 3 + OnExecute = actSessionManagerExecute + end + object actExitApplication: TAction + Category = 'File' + Caption = 'E&xit' + Hint = 'Exit|Exit application' + ImageIndex = 2 + OnExecute = actExitApplicationExecute + end + object actGotoDbTree: TAction + Category = 'Various' + Caption = 'Database tree' + OnExecute = actGotoDbTreeExecute + ShortCut = 16452 + end + end + object ImageListIcons8: TImageList + Left = 50 + Top = 209 + Bitmap = { + 4C7A0400000010000000100000007C0400000000000078DAC5546D4C535718AE + 09BFFCED7EB022529C20CE820A5591825459290502945A2AB4A032C9C63EE250 + 6CEBC06C88E0BC44C95C6024A8CB5C80586D635089F3839AB8B84FCDB2641831 + 314B9625339898B5B80552DE9DF7B607EE477BEF9565DB499EA4F73DEFF39CF7 + B3168B45A5D56AE7915390BBD4CC34D63A7C5D671A6F9F0A201CBEEEB325CC9E + DAECADB94BB9BE9628971E8D2DAF6AABF7E0AFDB46DB20160A7DEEDF343579D5 + D49FEAE0497FDB7C88F8CCC5E37281BEAA250B7C4DCD962AA5DC28E656D8B758 + 68BE3466F358075CFCE51BB8FBF411049EFC2CA951E8F7FC8EF5C05A71EDFD0F + AEC1D7847FFBC904CF7FDFDDD3F0C9C418CF56723C5267FA5D7DFD18797B127A + 7EBA04C38FEFF07CBB7EBCC8EADA6FF6CCDB9CBE6EB647F4DBF5EDE7AC4FD597 + DD60BAFA218F8F36CC0B7DA88D70C7B97CF777E7587EC5B52E51BE68433EFA70 + F9381BF4DB768361F9877F1816F1D1867775B74EF0E217D66FF0E14DB833F510 + DA897FD9D811166DDF0FC157C47676729CA769EE68646712E78ADA30EFCF1E05 + D8B7B09608FC8DB6127E4DA6B20B22F38C33298CD739DE4B621862514F7E0BEF + 53EC7956FEFC96BEC8FC7AB8F3CB9E25ECFE5870AE24B853A9B6FC1DE84BF7C7 + 22D85F7626495D9C91FDC5FE8EE36FB65605E2FD058079CC4E786BFE18290D3C + 1B8E0DBC9B7DE0DDC9E5703173EFD303CCEB59B0B34C2782FEE372F8A8310B88 + 8F4B8A7FD99D017D6F6A4538E9C9865157862CDFBB7F0D1CDB9325427BDB6638 + DFF2AA2CBFB729137657E48860624AE0C4DE4C59FEFDCE34B8D5B63A26EE913B + 397EE7AE755051BC4984B58356E8685827CB9F3CFE0ADC3F9A161378B7D8F7D7 + 9FAC50F4FE554F060CBCA515E1947B3D5C71CBF72FD8A70129FC97FCD0E92C98 + 1E2A5A34FFAFEBFB6076E20284FA5317F7FE401A84CE6C50FC3EEEEFF3F3E680 + 1484FBDBDCDCAC2AAB3CBACAE6F01BF4FA7C955EAF9741BECA56EB3720877273 + 0C4F1F6FDC1E0AA9533D2695CC494AF114A32F72906BADF31B74E49BD880609A + DC1B25B846F489FA8668CC89445347EDDB82A1581A2C97DCD17730569A2F9EC4 + 14B751C7B9C7380531B3FABAED41CC93D5A735A187D5E0C4873C9A2FE7DDF9D8 + 84FC8806DF9F9BAF5AB310533CBE30DE58F9BC281F734A54C84F8A133FF658A8 + 21E40BFB2BAC5F240EF76BB1F8C2FEC6ED1FF1A11A749E79F92A9C9F97492FE8 + 2E6C5438BF6A417E3B1CFEC252CEFEC4EA914883CC2D777F70075103E350BCBF + 8E85FDFD3F4E4242C2A2B9F5F6F216A6D3CD28D53015E5553247F65FF8C0D33C + B8B7A1FA7DDF17BD61D490E3AD5D9D6CA8B31A0F12FF190288228C1A72DC64F5 + B2A426A771A4BDC51E3E37D0C9E503C621F9FF98B86CB9E79DEA6796D2CDAE37 + 1A8A470F0B34482E5E29BED9985FD9C71C081E7AD71A261AAD44E3324763C654 + A4AF8CC75D93BE5247F3EDEF69FD9368CC59CCB9A8318AB9603DA4DEDE949359 + C5CD35AA11261AAEA67AE388366385418ABF52B33C9DF0A6B91A7D4C6B10EB91 + AC7E2949AEEE4E7BF97BD82382E751FE0CD6036BAA80DB825CD4C0383017AC87 + D279C699543257FFC64EFC93F33731D97C4E + } + BitmapAdv = { + 4C69010000004C7A040000006400000064000000BE2E00000000000078DAED9D + 075454471780E91D0489898205A5837416588AC08262C10A1A8DE68FD12836EC + 1D2CB1A6A8680CD895A231162C88628905C4168DDDD84B127B3489021AA3C9FC + F7BE7D0B88ECB2C07BDB9839E79E3511D9DDF7BDF9A6DD99F7ECD933AD828202 + 958DE397CE68171E3BA275EEE96DFDF34FEF58403484B083708070867083F060 + A325840B8423440B882610D6E79EDE313E79ED82CE9907377555F9BB9E3E7D9A + 79559962AEA5A5D7DA48C7DADFDEAABE9FBD93B59F83D07158BB0E1F883C5AB7 + CA9DDC33323779AC6847D222888D913B9277411C88CC493E0E71868DD3A21DC9 + 05913B92F6C2DF6D879F4B87D759113BA60C6C99F461C790CCB171F07B2321BC + 211A5B04D99A6805AACED7F7F5F5552E0F6708132D2D9D787D9DF75BB9D9BCDF + C72DD8EE5A684F516EF274B8A65BE0DA5E83EBF912A208FE4C6A13F03B5EC1EB + 7FF0FA047EF791881D494B030F0C4E7C7FAF5BBBF7C3DCDC2D626CCCB57CE1F3 + 74AA7B3C8C3FB0D4B58DF1B1B599E7D3DAA6978FB0D9F590BE91B949DFC2B5FA + 09A2A4B6D7BE1AF12FBCDF7D78CDF12BEC3BC926DDA79DCD199FEE36313E5E0D + C2DD8D359D87617D73DD6671423BB7D19D7B47E5242D039F5C802880EBF19702 + 19488B57506FAE423C843FEF0A593F6672D338612B70A599A6F2D0D6D3D5327C + CFC2C836C6B769F4F6A45EC0629728276905BC3E50360FAC979139498720AEC0 + 9FE704AE19E10D9FB59E81A5A9AEA6F1D0D6D5414719BE1FE26A17B523F97FF0 + 7D0BE13E7CA3027542069FA43FC1A16982D40101461F585A01171D75E6616066 + CC7030B77D4FD7DACFDE263A27E943F89E4781C57FAACCA1927AF307D4DF34EF + 39BD7D4D6DADCD0CEB996A9935ACAF563CF4CD8DF5DC3F6DD3F83D5F07CBCED9 + 5343E17B6541FCA34E1CDE6DF793AEC66C4B1EEE3F3ACEAEDDAA316EA64D1B98 + A83A0F7D537DADFA2E1F98D8F71585419F35136232DC5BF7D59843C5288631D0 + 3678BD189235B29FA5ABAD8D65734B95E4A1676AA46DDFDDBF4187AD833F867B + E944A4D8C1FF69100B89BF24AFC5D1391396B45EDAC3CBC2A5B1BE2AF10016BA + 763DC31CA02ECC80CF7A57D318C8887F804B7EE08AC44E961ECDCC5481879EA9 + A15EF3DEE19EF0D9A0DF9AFCB20EB1286D5B701E2170D99001563E2DEA2B9387 + 9E89A1BE435F9110BCB451CDDBEBDA06CEC3DC09481B34CEDADFA191B27868EB + 686B433D7D1FC65013C1552FEB300F9C7739ED35B34F04F42D0D14CD03EB05C3 + C2BD6943185FCF80FAF1BA0EB328574792CE7A26756F8F4C606C6FA2081EC042 + CFE1535130D60BA813B335B10F55CBFED74F6E63BBC484648E4C306A6869CC27 + 0FEC4761DB0D7EDA00318ED68B4A79FC07D7E607B8368F43D247F43269F29E11 + 1F3C707C817DDA88DCE495F09EAFE1FD5ED1EB2F95C96BB68DFF2D387D789C99 + 7D433D2E79E0B8DB215ED0409433E573788F17F49A57ABDF7545B83A31D8DAC9 + 9A331E380782E3EE3A36D6E36C5D253A67E2EEF0AF3AB5E08207CE0DE27C1470 + 3E41AF6D8DE3B97055E2226B81A3556D78E89B1B6A390F086F8C738391F49AD6 + B64DB917B87A703FCB483BED9AF2D0D6D5D6B2F66B6129CA9D3259DDD62D5490 + C735BFF99F06EB591AD5B87E9836B6D26DB373546875E7CC5BEF9C4A3AED9E4D + 3AE4CD24DDF67EC14974DD3B97C4EE9EA9CE4CFE6EB567C246C705EDCD6BC203 + D7F6705D0F586455F7BD7BEE9F47965CD943669EDD44F63C3847F21FFF5CEBD8 + FFF02249BD9CA7EEF5E4379F391FF733B032D3AD2E0F5CEFC635D69A8E333E39 + B488B4DF35837C797E2B3908D7F3F8D31BB58AC22757C9F2AB7B35612E787FCB + B1DDECE5E5813939980782B907B8DE5DDBCFD03E6F06997B7E0B39F0E812E521 + 8EC7DE337B4F366E68A567EED0C8A02A1E981F8539396C1E08276D38B62373CE + 65931FC0399407133F380FE9E01C923E3244160FCC1BC45C35CC8FC29C1C2E3F + 436CDE2C32EBEC66B2EFE1853ACF03AEED03AF19BD46857E377A9DB5BFBDA134 + 1E98C389798398ABC6477E54C7DDB3C8DC735B48C1EF57549A4774EE1412B7EF + 0BD2FD87AFF89C9BDF06F12078F5B0E04A79386B69613E2DFCEC05CC97E4E373 + B4DDF53999F863568DDA7745F040AF46E74E65FA2269977793C59777916EC085 + A73A72135E5F08370D4BD3EA57090F13E0D1CB4788F9FAEC5A382FF75DC2E125 + 64EF83F32AC9035948EAF1A4935924F9A7EF489B9DD3F8E271165E8B8419C392 + B41ABDCB0373FE31CF5C9CDBCC4F3E6DF77D5F91E5D77E50C9F60359F4857AD1 + 79CF1C706A3639F6F43AD9FBF03C19797C7529278E79E01E8A37C17B871F37BC + 686E509107EEBFC09C7FBEBE2FF67BA79FFE1EBEA7EAF5AF703EA15FFE629275 + 339FA45CDCF1D6FB6EBF7B8AFC0F38F1D9FF0D5C36ACD55B3C60048F7B61A07D + 39C5D7BD37F0701AF4772FA85C7F177DF459C1B764DDADC395BEEF9127D7C8AA + EB074847E81FF2C4A344983E6411B6DF121EB847CCEE5A582F114F6B4D38F7B4 + 06BE93AA8D079105DE271BEE1C95F9DEFBA0BD4B3AB58E69FFF818B387EC1D7E + DAECD80786121EB85F0FF788F1C102BFF3C863AB98FBACAA6B8E3F73F0D1CF0A + E3D165CF5CB2F4CADE2A3F173A76FDED42D283BFFEEFBD80D4413E121EB87792 + DDAFC7D97B44B1F7127EE7F5B78FC871FF5F23DF8133BEBEB0BDD231231F3CB0 + FF3DFEC70C78BFAAFB7B38B7300DDA3FAC23D8DE70CCE38FC0D484A1A5F5C3CF + 1EFBB9D7B87C0F9C6F6F0DEDC690C26555D60DECCB6CFBED24F934FF1BA6AF89 + E3F88A7D62BEDA0FFC9C38E759D51815EBC89A1B07613C32977C74603EE773F1 + 81A983D6218F1397CE683B0DE9D02192E3DCDB0105A930BE9A41965CD95DE5BD + 77E8F165E6DE0367965E23C95CBD22FA57BD0F2C20DF818FAAFA9CB9F77E2223 + 8EAD2473CE6DE67CBC1E9A35EA54EC984FF470AF7D4391676BF0551197EFF1BF + 830B493BF0C15770EF55F53D0FFF7E957C0B636111EB38F138651FE33045F0E8 + 5FB018AEF5E92A3FE7965F4F40FB9F4A269ECCE4BC4D0F5A36A45018D54AFBDC + 933BFA11DBA7F6E2FA3B8E39B186195B6D070F49FB7E47C153F89A77FF2CE955 + C1013DF7CF272BAFEF6798A04BF8E4818EFCEAFC36E6F3C8722BAE197C7E7A03 + C9B871A8B47DE4AEBD4DBE3AB47069BD734F6F5B44E4248DE7F277C7EC9ACECC + AF631D39CE5EF3CA62D7FD33A410EAC66229EB7EBD0FA490D5E06C6CE3F73FBA + C8EBF81CFBBD3BC047DFDF91DDF74803FFE2B8BDF39ED95C7F863B0905A9DE78 + 1E08B41D8BB89D17F9922C836B37E5A7EF647EB7D53026C1EB3C001C20D57BEC + 3AE36CE0BBE8522E6F3C708CF4CDCFBB98FE96ACCFBCF65601F4C92F91614796 + 73FD19EE271C4EEB8C67B388C4FB3738FBDD380FF43DF47171BD5BD658031D91 + 73F71489D939BDEA3A073F83731A7CCEB14F3EB996B9970E3DBA2CF573633F10 + FD89F707C79FE1494241DA503C272792E3F9F541854BC94E681F658D7BB13F3B + E1C74C26EF4155D689861E59C6B425EB65F4B5F6C0E7C6366D29F7EEFC03784C + C2338B80C7412E7FF708188F63DBB7BB5C7FB5B2FB6C3070FBFCCC4695E1D1F7 + D037CCBCE7A29F774AFDDCF9D0373FFAE43A53FF397EFF67030B5267E2F951F0 + E7E35CFEEED1C7D7401FF68ACC7527AC3BF8FD471F5FAD323C7A419FAE2DF445 + 66C03D22CBB3C7D8FB89E3F72F4AC84F5D8067788972924E73F9BBC79D48AFB2 + EF8873281F1F4C21894757A80C8F1E3F7CCDF40D65F5438EB1FD45F431D77BDB + 81472A6F3C80058E19A47DAFEF591EC35588C78772F0908C9970ACCE1B0F8ED7 + 3DC69FC860EEA3631A5C3F76F1533F16030F5711C7B93D6398F6E32A3924A3FD + D878E718E99BFF0D338E57151E384F8873BEB3CE6E92B90E834CB6FEFA23E77B + 12603CF805F07082FED53E4EFB57475792FD0F2F91BC7B6765F6AF8640FF7286 + 0AF5AF707E19FB578B615C286BEE13FB57EB6F1572FDFE7F417F772A9EC3293E + FB91CBF1C712667EEE7B19EB1E38FE9878328B8F7E7C8D63D8D1E5CCF8E37B19 + E3A63DF7CF317564C965CEC74D4F81C7183C1315DAF3742E7FF727ECF8FC5B19 + F719F6BDE65DD84E76DCFD89990756360B5C63C2FC1ECCCDCF7F2C7D2D043D55 + 00756426F7F5FA11F0F804CFA78DCC499AC9E5EF8EDFF725590AE36E9C7F9035 + 17947EE310336E1C7C78A9D279E01E139CDFA9EA33630E0A7EE6214738FFCCBF + 0D2A480DC7B38223B6272770BB0E3A9D59E3C3F95D9CBF95F6DD764BEAFE95BD + 707F4E531A0B9C3BC7399E5CA8ABB21C8B7D5D9C73C479E98EBB399FDFBD31F8 + 705A333CB719CF0A66CFA7E5ECF7630E03AEF36DFAE578957D476C4BFA177CAB + 341E9DF6CC66DC899FE7A88C3E3AE62B259FFA8EC9E9E37A7DB0D5FAB167E2E6 + 0C33C033B4F1DC6611C7FB0371AEB0CB9E394C1DA86A6C85F71BCE772B8B07D6 + E7A93FADAF720C8B735758EFD7733F774522B74DFE7960EE7C13717E8943249E + DBCC47DFB162BE5F6581F3D75F5DD8CAE43F28C355D8FFA82A074BD24747AF4D + 01761C7F8ED781A9837E28977FE5CDC55EA8CA72C571AD49D63D57368F7D8E71 + 56143F396732F24B66912FCF6F912307EB3AB37E8C6E8BE73E0FAB283035E1CB + D2FC2B7FFBC691B9494BF85A9BCE843E4955DF17C7581B6E1FE573CF45A5F957 + 634FA433EB7D557D3E6CE3269F5ACB5F0E6F6A4267098F7AC2C626C2834313D9 + FD861CF7EBA7317354F2ECC1C1399695D7F633F9257CD7136C33128FAE247932 + DAB7F2ED1CDE53717BF9D90B12B165E24DF7B15D6C4BF3A903B5B4F09C7FF66C + 795E72CE565CDB27579E2EAE9BAC80FE0BE644F1B1F70239E33E769C63DB79EF + 8C5C9F095D3AF1C74CBEEE8D7F84CB87E6BDB3DF20CCCD1DCFF9E72BBF1DD7BE + E5FDFED8DE60BE39D62BC6D730BEAC6D7D8985F60CEB2A72C6F9427973EDB1CE + 624E09E6F6F1C4E3AFA015438756E46111636BEE57F8E924F139FFDC3FF301E7 + 44B0EF2EEFBEE7636C3FF8AB0BDB985C95E1C75632F9CCD5A933E2BA308BB917 + 66433F15FBE038BE3E2A2307E99D360DFA5D9FF0B3FF03CFFFFC2F7CD784EBF6 + B95176EFEC57F3D5D2B249F7690F2CAEE299B27CDC0B381EC1FE6F75F6734AD6 + 4799FC2BF0188E13705EB8CFC114C6E778BDB15F2D09CCC1C3752564804EFA1A + 786E863129D6B9EABC2FF6A7706E0DC7B53CD58B5FF07CDCE0AD89E95AD32AD9 + 3FD809789CF5E90E2C1E45729C5B5D7EBD07F3978EC9797F32EEFA5DBCDE8039 + 8C38378F6B14C800EB1BD6151CB360AE8E38A6324EC235256483634C6487FD54 + 6471B09A6712605DC67A25DAC14BDFE2298E3B827727EED52AAC7CBF333E1746 + 247E2ED31CBE729C305716C75535DDB77618C6C8B82F0173D2C79DC860C667FD + A12E60E03E27EC33611D4ABDBC9BE4DE3D2D333FB2AA312AEEB1952737AC8671 + 04E2CFE055899F49DB7F8ECFE8C1E7C2E0B348F0F9177CCD6B271E59CEE40354 + 355FA4E890D45B64B10AEA14F60178DA5BFB2AECFBB1A911DB265FF4FDA28FB5 + ACF3329A7613B6C2E7C2C0BF59C257DF1F3D8379FB584FB01F75F8F7AB4A6781 + 6D3CAEA1E1FE2CCCD14327F238FEB9E83CA45DC7E0358963AB3ABFE483484F73 + 7D4B535DFF450981A21D494FF95CFF41B7E37E7B9C6FDF5F8BB34D6A7D7610DC + 0FD8E68F81B1FA97D0FEB7E5777DEC55AB75A3D3EDE284F5BCA7F4785FDEF396 + F0193DF85C183EC6ECA5C18E29DA41DB8B7374585F145957D04F380EC1F1673F + 9CEFC7CFC3F3BC0078E7B2DBB8AEDDAA7BFE153E2F099FD183CF8551C87A29BB + 1FFFDBCB794C3B2CCF1C64CD3988FB4E98A78B6D3F0F7B07A4CE1B86AC1D95D6 + B89BD0AC46E7F5D95A9BE1337A30374851737C5857705C8E5C703F12FA1CF75B + 1CAB613FE9EDBCDB6BCC1813C784384667F601E62A6C2EF94DC4CEC9C7DD6675 + 15D4F4FC447C76153E2F099FD1A3E8F34571FC807B10316F00E755313702C72E + 393046C37BBBAA7E19CE83E1BC13AE4FAE041FCD8171048E45718C88E3200572 + 9078EA5150D6B0910DE2DD6A7CBE283E470C9F5D85FD0165ACA14AE637965CDD + 4376C37810C773D81795672F3BD627741ED60B6C23D04D38DE9E7F713BF9F4D0 + 37BCB71395B92A6CC7D8C576D323746A731E323E470C9F5DC5C533646BDAAE44 + C1F818D75186C2B805E7B370EE7BC7DD53CC9A04EE552F1FF8FF901DF697B0DF + 8AE72AF43990C2F4B1DB2A2FAFE8DFF02D938EBB8D8F73E2E2BC704B375B9BA8 + 9C096922157A064E343B6F1E0FFD65CC99C2C079AB2E7BE7306B1BAA74863BF4 + 536F07CCFF5F77AECE6FC767BAB55EDADD0BFA5AF9BCF67F3533FE146D1E333B + 6854903697CF9B3077B5D50F5835A43378EB3A9B8B42B9C8AC13CCEB8B88EC89 + 1B5A8EEF62C1C7F3582C3D9A99072E1D92004C6E47E6249F12D133C4A59E5709 + F17778F6C41CB7B15D9AF0F9BCA2FA3E2DEA07A4268CF39EF15104F8EB1CBDFE + EFB078225C39ECCBF0EC09792E89B12E8A789E97B5BF838D9EB9B18147528F58 + 78FF93F4ACFD5216BF038B458DA23CED9C06B6F151F4F3EEF03962EE63BBB481 + CFF1038E3FEB727B818E12AE4AFCA251B4979DB29E3F88CF740BCD1CF1193EBB + AAAEB7256C7B91E79410E3ADCCE7A51A35B4320ECE1885E7D1DCADC37DAE17E1 + 5B26E6B88CE8E8AC0ACF13C667BA05678CEC8273C9A2BAF5DC5474C29FC062A3 + EBB8CECD54E979DBF84C37E1EAC41018C7EFC1BD897580C5BF5139C9B7DBAE9F + 34CB6D623C270F41E7FA79F4F84C377C8E9870D5B045F8BC24364FE56F0D6280 + 7D9647383718B175D289902FFB778F8F8FD7E6EAFA71CD4352DEF377B00A593E + B49FE0EBBEC1ADB74FC1B3837ED380B6A5283237F97848E68851E1DB27A67A8C + EFE6C2F575E38B07F3AC8400176D037313ADE049BD2C7CE6F4E907DF673FE671 + AB2107CCD7BC82EB7A1E9FF71434EAE4AFED3CB1B30E1FD78C4F1E6FADFD5A99 + E9E2337A604C3F292A2709F7BA3F50F5FE31BB7FEF22E61EB88DEB1A276D8D55 + 1D79943EEFA5A1A59ECBE0764E5E333E1A15919BBC0DBEEF4DC939FF2AC0E01F + 368713736A8E607E14E6E4601E88A2AE8FA27958D83732085D9D181CBA6EF4BA + 48711D79C1B278A3227D573CF3169F05FC27E6AA617E54C59C1C4DE2513A27E9 + EF6014943E2244B829314D98919814B277C471B66D295142BB8FF5E22FCC33C7 + DC66CCA7C51CCEF279839ACEA3B4E073611A6969E1F32F82970F89102E1FBA28 + 387DC4193CCF9C59C711F795B95A6B91B457AFD93AF918F725E15E18DC7FC1E4 + FC639E79A1F22E87D2795436AE6CFE816140EA20DFC0B4418978867668D6C853 + 7856B02897C903BB2D12EFE1C2BDC07FE01977ECB52D2E17CF58964FD9B1C26F + A2DCE41B61EBC79C8EDC3EE9E780B484BDB87712F7EBE11E3155FAEEC8E3F4E9 + D3CCABAA46FBB17DF482A2C2B4071F5F5C6F507E9AE7C082D4CE780E279EFD08 + 3133213F6DC1C0FCB4343652E1BFE74224E1D92C107D130EA7450C2A4C6DDAF5 + 8BC106FD77CF3551E5EFDABB776F2D4208E7F1DFCB3FCCDEDC3BEE0AE1F2EFBD + E36E1A198FCE3AF071EDF888D7B7768714A5DA1D84F8A128ADF9514D8C927591 + 1BD485C79B5BBB3B028B971045F0D9892606F0B84679501E948766F27896DA9C + 3C5AD4823C58A85EF110E2C9372D348EC7DD147BB276A43B491DE4A956B174B0 + 07C99BE8A2713CAE7EE94086C6FB91CE31812A17B19D84A4ED87A124B6A3F09D + BF8B6F1740BEEEEF4579F01C1DDB07914E6D03814318F14CED4A1CB23F22FE73 + 3B920E5D83290F2544CC476124365648FCBEE848F45E8F23BA643CA9F7CB5012 + 323186F25042B48B0B61EA48F0C4B6C4E0EFB10C8FFAD7069388A15194871242 + 34209274E81C4C5C337A302C304C7F1F417C1774A63C9410D87E77EC104482A6 + B523264F4712C3E2D1A4C185041236BA35E5A1E4BE9560562C71C9FA907117B6 + F17581C7F5AF1CC8989EBEA4576C805AC5C79D0464D1404F8DE3711FC6B95BC6 + B9918CE12DD52AB246B42407A738D3F92B3A7F4579501E9407E54179403C5DDC + 829C9BE3484ECE7052AB3835D389E9ABD3F1876A041D0F2A783C182B246163DA + 9080991D48C49028D2A91DE5A1CCC0F178FBB810D2B667E83B73ED9487E2D73F + 5A7F1C4E9A1CEC4BEAFD3A8C386DE845DAC587501E4A88E8BEE1CCDC95E7926E + A5F3BBE60F8613C1EC58CA43293C22C43CD2BA11BDD7E2F50F8B7B89246016E5 + A1145F75607DF5BF56A4C9A1BEA4C1C504E2B4B12769D72394F250761FABA390 + C4F40E6318D595F6FC97F9F66451822799F2B1B75AC5E79F7893EF47B96BE4FA + C7B85EBEA47747815A45DFCEFE647182E6AD7F608E1FCE3D144E7756AB380271 + 69AE239D4FA4F38994471DE2F1D7B7E236FDE6D70E6A15B720EEA5685E3EF5AF + 0BECC9B2211E64EEA7DE6A155F415F77CB58373AFE50F07C6274BF083A9FA842 + 738B3877D2A95D10E5A1C47A81AF98331A322186787EDB95B41A11FDCE189DF2 + 50503E7577365F34B91DD17F259E4FB4BA3184840F8FA63C9410519F89F3A9DD + 56772F9D6F377B3C9CF8CDEB447928A37EC48730F5035D65796B2893DB6E73B4 + 1FDD6FA0ECB63C56C830F149E9C2B8AAAEE453AB6C3E436721F19DDF99D86FEF + 4D02A7B7AF333CAE7DE54086F7F02371F0FDD4297A760820F33FD3BCF9DD5FE7 + DB9325303E9F0D635E758A2FA06E646BE0F89CCE27521E9407E5A12E3C1E2E6A + 41F64E7661F648A9536C8338365DF3F647FD952A3EC304D740D42DF04C193AFE + A0F9ED7572FDA35D10737646F4A711A46D8F503A3E57620E1C5EFBA8FE91C4E6 + 587F2657D47E5B6F12D32B8CF25046FEEEA7E2FC5D8F657165F3BB0F8713FFB9 + 347F5719D1A64F2BA68EF8CEEB440C5E8E21BAFF8D279677861261723BCA4319 + ED06BBFF23A667287159D783D8EDFE98782DEE4ADA77A3FB3F9499DF1ED52F82 + 585F1EC4AC7F34DDFF096D3F54203A741692D69F86337B09EB4A7FF7C6D70E24 + B98F0FF9ACABBF5AC5A0383FE64C4B4DDD7FFEE30C27B58A931ABAFF9CCE2752 + 1E9407E54179501E9407E54179501E9407E54179501E9407E54179501E9407E5 + 4179501E9407E54179501E9407E5519B285ED282142F7723251941A4385D404A + B24299285EED478A973A521E0AE4C1B058E34F5EEE194AFE399F495E9D5E4A5E + 5FCF25AFAFE5905727169017D95D48F13267CA43513C56B4242FF78D20AF6FE4 + 91D7B7F6BD13FF9CCF202F3675A43C14C403BDF4FAD2FA4A593071338FFC5D30 + 95A923C52BDC280FBE796408C93FE7564BE7716317F9FBE044F09A3DB4250E94 + 07DF3CBE8B82EBBE573A0F8857C7E791E2555EA418DA7BCA833F1ED896BF58DF + 1A9CB45B0E1E3EA4243398F2E0DD5741E49FB3ABA4F3B8BE93FC7D6002E3AAE2 + 654E9407DFFDABE5AEE4E58EBEE49F9F378ABD7573CF5B6DC7AB1F53C069D1B4 + 7FA5C8F120F4795FECF81F78E96BF2EAC86CF2EA542AC462F2777E3229019F15 + 2DA1E34145CF9730E3C2951E4CBF16FB5C251981BCF889F29097873D2981713A + 8E314AD6B7611C852EA33CE87C22E54179501E9407E54179501E9407E5516778 + B40316BF433C80CF5EA489013CCEAA0D8FBB475D4B36769853B2A1C3CC171B3B + 7CAD89F1326FC0783EAEDDE3C78FB576EEDC5965EC3F704977F7EE43DA474FBC + 3138987F43E7F0B1677AF2FCBBBA18FBF69DD4CECBDBAF75F4D86BFD8385F7F5 + 0A8E171BC8F3EFF2F3F399D7AA8AA151335D17AFCDC2660EB36D03A38A47BAF9 + EF6EE51B7EEB23EB0FBA1A68D1F256D1D3B3D676705EEAD8ACD9D4FA4111CF07 + BB079FEAE61D797FA0B5EDC7C655FD5B575757B978E8E89868377598696B69DD + C64C185D1C60EFBEB4B177D8055703A3C63A94C0DB455BDB40AB51A30196F5EA + 85190A5B3DF772F4DFE9EC1971D3C7C0D84EB7B63CB05E200B5BBBF1502F4ABE + 098A2A49081015FF1A105DB45C105574B465D06181B1A9938EB955B05E5DE7A0 + AFDF405B5BDB50AB61A3FE9641E1CFC7018B1E82A8E29B82E8E25DF07AD123EC + B2C8D4DC5BDFC2AA95414D7894396A16B2580C1C8A21FE08882A26F0FB8BE1F5 + 3530392C76D7ED5EF5EBB0BB1847392D756CDA2C191D3505AECF53880778AD20 + 5E41FC1B1055F2B357D04FDD04E1F707BEDFA87277C9E251DE51502F06322C44 + C577591E4FC5EF53B4C1BE25E32E1703235B9DBAEDA881E0A850C3E056CF3AC0 + F5F915E21ACBE305C41BE071D8CD77978B7FD82D5FBCD7E5E55189A390C513F6 + 77578C17E0AE156277150A8CD05D9642BDBAE7A87EE51DF5AB946BF51AAE671E + 70B9E01B72258A71976598812C1E124761BD4016E51D252D2A77571783BAE1A8 + 25E0A8242B7054720547498B8AEE1AD0A0511F63693CB05E607B5199A364F060 + DC2560DCB5CCD63BEC629D7017E3289B044B8B3247FD56CE5144AA4F5877B9FB + E6A1BB7CCABB4BC2A382A31657E1285295BB3C4ADD15A4A7B18E6A58EAA8EE32 + 1C45E4739717B82BD40079E0B8BBBA8EAAC25D6FD05DEE7E7BC27C5BDDEEF9DE + 7B5DF43587C57BACA32657C751B2DD252A75D767E1A2D9C6380782E36ED65109 + F2384A0E776DB46FB9DCD607DD6560ABAD798E0AA98EA364BB4B04EEF2DBCDB8 + CBDBB7ADEE91136F0CE0FA8F0814150BE0F5971AFEEE77DE2B30B2684580A8E8 + 8897EF113F6363471D0B8B205D0D72547C0D1C25B59E044615AF866B9FDFABFF + 8BF7F61FBAA1E3E8B33BB4A9CB121BBFC8E7CBE0EF8B6AD0764875173039DCD2 + 6B4FA87FF0ED1ED6D69DF5D5D6514D27A1A3926AE9A8777C05BFEBB257C8F95E + 7E91F78747B55F6E947FE499AE67C8AD9E6E81E79D05A2A2426C6BD89FAD2D0F + F19831B268A393F30A1BFF808B4E060636DAEAE9A8419616169C38AA7CFCCB5E + A7277E910F6708447FAEE9DAFB9219F6AF2C1B74D5D737B4D576F63BEC0F4C0A + FC238BD6C3CF96F0E52E73F3405DF571D4A77C380AEBC5158887C06256138764 + 63078F55A6EF8C074D9C58772DB5F1E7D75DDD55D95D124735693AB1BCA3EE73 + ECA88FC051236C9A8F3292361E34AD17CC97BBB0FFFC0FB86B9393F34A70D725 + 95765799A382F974D4E7E0A87417DF4D66B2E6AFEA35E82271971FEBAEEFD9BE + 19C7EE3AEA2B765780AE0A3B2A0EAEDD2F1C3BEA51A9A33C579BCA3DDF5EEAAE + 654CBF0B9DC3B6D1DCB9CB5BE2AE4EFA2AE228A7264D27A0A326F3E528FFC8FB + 236D9A8F36AAEE7CBB693D21BAEB43B7C00B4EACBBDE70EFAE55E82E47557017 + 3ACAC666B0C451B1ACA3AEF2EDA8EAAC0F96BACBBFD0CF1FEE67880D1CBB6B25 + BACBBBD45D025D6539EA83324775E3D3518E151C551D1E15DC15D6D465B98D9F + E8F90ABEDC2508BE1D6F6DDD515FB18E4A736AD2643CCF8E7A30CAA6F918A3DA + AE9F97BACB2248D703DD1574C109EAC8111EDCB5D9C96535EBAE46DA8A75D410 + BE1D355D96A36AC243ECAECE15DDB5911F771DF33136760077F9EB2ACE51CFF8 + 72D46CB1A3D698CAF3B9AAC3A3D45DE079A6DFE5FA96BBFEE0D65D7BD15D717C + B84BE2A8C64DC659018B49780FF3E4A8D1B67238AAB63C4C2D02D15D3DDC822E + 9677D73F5CBACB9971D7CFBCB88B7194ED5045382AC3D9A76A47D59607E3AEF7 + D05D36E5DDB5894377BD2C75971F77EEAAE0A8F1E0A8AE3C38EA7199A3D2CDAA + FB196BCAA3A2BB9AB9AE5080BB62F56BEFA8B17C39EA0AEBA831B6CDC71AD5F4 + 73D6968789D85DDD797457B6B3CB1A7497436DDC55E628A101EBA8BB5C3BCA3F + F2E1B49A388A4B1E627775D2D73740771DF1E5C95DABC4EE3ACEB8CBCCCC4FB7 + FA8EEACBBBA31AA3A3BC32CC6A732DB9E0F1B6BBF680BB56A2BB56F2E4AE1070 + 573779DC55E6A831E8A889ACA3EE71ECA8DE8CA35A8C33E2E21A72C9C3C42240 + E22E478EDDF56799BBD2E57697D851C3248EEAC8AFA3369AA91A0F29EEDACC93 + BBBC8D8CECC15DBEBA7238AA0B7F8E4A0247659A7175FDB8E651E62E07D65DAB + F8725721EBAEAEF5EB77D07FC7518D475BF2ECA8B15C394A113C4C2C04E0AEDB + F16E4197F872D71667970C70D765705743EDB71D95C8B7A3A682A332B9729422 + 7888DDD511DCD548DBC5FFA84F3977BDE4D05DABC5EE3AE18DB95D188A729493 + 5796191FD78C4F1EEFB8CB6D356FEE12B67ADE078367478DB36D31DE88CF6BA5 + 081E26E6FEE8AE383EDD1512F6CC15031CD549DD1CA5681E52DC95CDA5BB0244 + 4CEEC735666F23478E82DF75055E1FFB8783A3ECF973943278BCEBAE35E8AE55 + ACBBFEE4E81A12AE1C852C7C02CF7F24087B30AE4993F1868ABA3E8AE66162EE + 2771970397EEE230FE655F9F08C21E260B22FECC7073DB60AAA93CC4EE8AD5D7 + 63DC75CC07E74070DCCDA1BBB870D4EFC06276D3A64946AEAE6B4D14796D94C1 + 4352706E10C774380782E36E25BAABA2A3C6376932C15019D744993C709E16E7 + 06713E2A40B9EEAAE8A84C453A4A557860C1795A9C1BC4F9281C4760DF55C1EE + AAE0A8C9E0A875A6CABA1ECAE62129383728765706BA6BB502DC55D1511394E5 + 2855E481F3B4383788F3510A725779472529D351AAC8030BCED3E2DC20CE47F1 + EC2EA65E08C42C94EE2855E52129382F88735138FFC18EBB39E5011C5E08229F + CF13888AF6B6F43964A64ADF5DE57944F1C32320E2F97CE4E1E94D79482BB896 + 84EB17EC9CF9449C1BE4703EAA7CFCED2F7A9E013C8EB9087EF434306AAE6364 + E6A14B799467D18059D7C3B5245CBF60E7CCEFF2D59E4BE6EAFDA19D72F2FD21 + D823E476AC79FD183DCA43B2AE67A8856BACB8AEC7AE25DDE368CE5C1A8FBFB0 + FF063CB6376FF99D8D9BF0720B3D830FB4EB3A8F4A1CD591C3753DB9DCE5277A + 9E8E739B2E01273D187799B6D4AD8B3CCA1C354C218E92D35D42705707F3FA6D + F4EA1A0FC6514DC65829CA51F2B96BBD8DBB12DDA50C1E2AE02859EECAA8E02E + 1D4DE651E6A8A14A7554F5DCD55A4F5379881D3556E2A8AECA72941CEECA69DE + F27B85BB4B513CCA1CC5EC8599C4E6FCFFC2E1DCE00B36FEE6CE5D458CBB5C03 + 4E81BBECC05DEE3A9AC0438AA37EE372CE3C30E2F93C88F981914519EC5CFD5F + 1CD495923277ED4777B5E7DB5D8AE02176D4382BDCAFC73AEA3E97F951CCF943 + 91457B9910151DE330BFEB5999BB3680BBAEF0EE2E3E79F0EE28F1BA1E3367EE + ED7DC80CC3C7EFA42733570F7D579EDCD552EC2E371D75E251EA289B21CCFE6E + 3E1C255ED77B9884EB1765EB8CCD753CBC7F0876755B8FEB8CE95CB92B40726E + AAA8E830B82B08DCD5D6BC7EB49EBAF04047E11914ACA3BA71EE2866BDFBC104 + 5CD72BBF96646AEAA12B08BE132B08B8D282D3754691B8DF25882CDADEC26323 + BAAB391FEEE29AC75B8E8A783E994F47E17A77656BAC5656317AFAFA1F68FBF8 + 9FF2E0C35DFED867807AE21AF093BBD85DAE3AAAC843ECA8254E786611EBA8A7 + 3C392AB9BCA3A415B1BBF60B5DDDBEB7E1B2DF55A9BBACA2F4548D07E3A8A613 + 58473DE7CB51E32B3A4A5A61DDD5814777E5B4F0D8C4BAEB7D6D55E121715443 + 253A4A5AB1B26A53D15D39FCB9AB998E9149EDDC555B1EACA31CF1EC47FE1D95 + 54E3BD3065EEDA207157093BBEE0D05D0702C15D31E656223D65F1103B6AA2C4 + 51713C38EA31E6FC639E796D729B4D4D5BEAF0E4AE6765EEDA5C6B77D5944705 + 4725B167A2F2E4A8F1865CE4AA29C05D9962779D66DC6568E2A2A3081E0A74D4 + 94DA384ABABBECC4EE72DFC8B7BBDA9855D35D35E1818EC2B3E5F13C73F60CED + FB5C9E15CC3A6A2C3ACAC5258BF3FD172626EE3A02E19DF63CBA6B470B8F6CB1 + BBF4ABE7AEEAF0287354BFF28EE272BFDEE5B71DB591B71C4E2BABD68CBB7CFD + 7F6AC9BA6B074FEE7233306A0AEE72D6E1924799A312D4D251B2DD7540E8E6BE + 09DD95C9A1BB4A5877153AFB1E4477B536B38AD4E38A07E3A86693ADF0B930AC + A31EF0E0A8317C394ABABBDCC05DB7DB0902AEF2E72ECF2D72BBAB2A1EF84CB7 + 728E4AE6D751E37875947477459777D7111EDC952576D7992ADD258B87021D35 + 55918E92E92E9F03416EEE9BF97397DFC1008F905BD1E6F52374ABCB43ECA824 + 2B7CBE1EFB4C371E1D9569A26C1EACBBDAF2E8AE5C7BCFAD32C78C95F1287554 + 23DE1D354B598E92562C2DA358779D7667DD95CBA5BB04A2E7E8AE8296C1A75D + C05DDA46264E3AB278881DB5D4119F8DCBA3A37A018B69CD9A251B69A9683134 + 6CC6BA2B1BDD95C583BB0EBB0A0E0ABC5BDD1299D70FD795C643ECA8E4FAF5C4 + 8EEAC183A31E81A3468B1D9561A2AA3C4C4C5CD15D31E0AEE60165CFDAE0CE5D + 50EF1C7DB736F20CBBDCACBCBB243CCA1CD51F1D35855F478D05476D32D552F1 + 2276D7FB0A70D7197057137097A30EF2D8B7EFA4B683333A6A407947FD5AD71C + 25D35DBE0783DC5A6EB109882E5ACBBAEB3997FD2ED700C65D9101218374F3F2 + F66B356B36151C15C6A7A34681A3D25D5CD24DD48D8789898B8E2018DC1578B5 + B920AAB4DFF59A03773D97B8CBC9771BE32E4FAF70EDA3C75EEB83A306030B2F + E070930F47356E3C462D1C25ADD4B316E9E983E7BD42CEB44426103B3974D74B + 70571A70D9D3BDDF73F38385F7F5DC834F7573F4DFE92C882EDEC57AE605478E + 9AAE8E8E92EA2EE3663AEE8107835C7CB7DAA2BB026AE72EAC67FF05884AEEFB + 86DE1E27087F92D2AE735EBD82E3C506DE91F7077A46DCF481DF7D91F5CC9B5A + 3B2AF4C1487494B3F31A134DE1616CEAACE317713BC637FCAA3DD491C25ABAEB + 3FF6F5B920E2E92241E4F3ED711FDDB1C6FE95B5EDC7C606C676BA1E619745C0 + E46788821ABC8F46394ABABB22C5EE0A2D75D7AE1AB88BA91762167FACB6735A + 58CFD57BA775C5F187B18537BAABABA3FF2EA76AB84BA31D25D55D464DC15D87 + 825CFCB6350677AD93D35D6F3B2AE2698A9D534A3D69E341B3FA61E8AE01E02E + 6FF8DD17E474573947DD1F2176D46A134DE7C1BAAB4D35DD55DE510BD151AE3E + BBAC65CD5F89DDD5ACBCBB0ECB789FF28E9AD9B8F1688D74945477D58F60DCE5 + 1D7AD6BD9CBB5EC9E7A8947AAE3E3BADE59DDF35367FCB5D7915DC55271D25CB + 5D2DC15DAE7EDB2B735799A3C218472D6CEE5CE6287979985985EA83BB3E93E2 + 2E89A31E82A38683A3D6D4054749779713BAABB514779577544A0038CADDF7ED + 7A21EFFA607D9B3E1277459673D7ABB71D35AA4E394A5AB178D75D794CBFAB9C + A3B05EB8FBEEB2AEEDFAB9B1B917B8EBA7AE4E823C2760B20A1CD513587C5E97 + 1D5585BB025DFD721A0744152D651DB5A885F3C27A55FD5B797998598580BBEE + F5F788B889732AF9E0A844B1A3569950026F17235347745734B8AB05D491BD82 + 88278CA3DCA438AA228FFCFC7CE6B5AA088D9E65ECE5DB56B77BBF92FAAD5B2F + 378C8FBF642ACFBFAB8B11143248CFD33B42BBE767C5961DBAE4D58BEF73C75A + 9E7F171B1B4B6F685A68A185165A68A185165A68A185165A68A185165A68D1F0 + B2796D4A2B428836BD12CA2F5BD6A60CC8CE4AF92F3B6BE172CA4435586C59BB + 90605026AAC38232513D169489EAB1A04C548F859807FCDCBA45E1F4AAA9080B + F8797AD5DE2EDB33E73B6C5DB7C84F9ED89231CF89B2E0AF6CCD5C18B7252BE5 + 8D3CD79009FCD9AC945E94050F2CD62DE8B8256BE12BB95948614259D4BE6467 + 2E6A5B2316159850161CD48BAC8551D96B534A6ACC828DAD62CFFD4E59D486C5 + 82302E58948B373086784259D4A02FBA2E4598BD76E1730E5954C9A4AEB3189F + D8B5F1A4E1F1472625C6BB95FFFF9B3216F8437BF1170F2CA432A12CBA369E9C + 18777DF2F078024C1E49986C5D9BE20D2C9EF2C8E21D269445190B492093B479 + E33A413FE8B10258943211B7F194457916929832A6E7EF9B32E7DF50200FA963 + C63AD35E24C65FAB8C0565A2D8327648F78655B1A04C1457A64DEB6E3029316E + 8B3C3C94C9647BE637CD2913D561B275ED7C0F4D6E2F268E8A775027269ACAA3 + 5CDBFD5BF2F06E8EEAC244137954D28F521B269AC643469F562D9868120F5963 + 3D7561A2293CE460A1164C3485075CCF65F25E4FF8D9BBAACA4453784CFBE413 + A3C9C3E3F2D49D8926B51F9AC044D3FA57594B67F9CE9B3EF8A43A32C9CE5AF8 + 5A93E64BC46B49298FB3B352FE9EF779ED99C0DF652B8C0993BF92D24D635860 + 3E60B9753D7562829F155E35E66024CC3DA86CBD5B1D9860FECAE67529311A53 + 2F989C1CE97920AACC8461B17651A4E6B0902F574D1599E03D84F792A6B06073 + 385F54C7D1D5653271E8874EBC3041B78263358645564AA79AE4D32A83C9D4D1 + 3D9FBCC504FA1CD8F7D01C160BE26B93DBAC5426D017C73EB9268DF7AAB5FF42 + C5982CFD7A4C474D5B6BE26A5E42194C260F8F7B9C3C32CE9DF2A04CD48187B2 + 98600E37E5A11A4C98B5E451F1B69487F299681A0B3E7970C904AEFBE64ADA8D + AB9AC6826F1EFC31D14C168AE051132670BDEF4967A2B92C14C5834B26D30675 + 7D5F4B838B22739BB960A2E9654B56CA1D55675271AE5E93CBD6F52976AACC44 + 13FBB4EACB44B3DBEEAA986C5D9BF244E14CA4E612D55D160C8FB50BFBCB7B36 + 0B87FBC91EAF4B9BEBF76ECE1D65A16816F07E7F48D692DECE83A42C145F2F16 + FE959DB550F0D6D8029840DBBD64DCD09E36948502EB05E6126950EE0157253B + 6BC1678A6781F95129ADE8D5AF390BFC39A8474F3870D48BCDEB1644D3AB5F4B + 16EB160DACF5F8449CDBDC8E5EFDDAB3A8F59811586CCD4CE942AF3E772C6ACC + 047389D62DEC4EAF3EF72C68A12C280BCA82B2D0FC82CF50A0E736AB4EC1678C + E0B346280BF5614259A80E13CA4275985016AAC384B2502D2674AE9B165A68A1 + 85165A68A185165A68A185165A68A185165A34BFFC1F558FF11B + } + end + object SynSQLSynUsed: TSynSQLSyn + DefaultFilter = 'SQL Files (*.sql)|*.sql' + Enabled = False + KeyAttri.Foreground = clBlue + SQLDialect = sqlMySQL + Left = 50 + Top = 298 + end +end diff --git a/source/main.pas b/source/main.pas new file mode 100644 index 00000000..58515880 --- /dev/null +++ b/source/main.pas @@ -0,0 +1,15100 @@ +unit main; + +{$mode delphi}{$H+} + +interface + +uses + Classes, SysUtils, Forms, Controls, Graphics, Dialogs, Menus, ActnList, + ComCtrls, ExtCtrls, SynEdit, SynHighlighterSQL, laz.VirtualTrees, + RegExpr, + Generics.Collections, Generics.Defaults, + dbconnection, dbstructures, dbstructures.mysql; + + +type + + // Bind parameters for query tabs + TBindParam = class(TObject) + Name: String; + Value: String; + Keep: Boolean; + end; + TListBindParam = class(TObjectList<TBindParam>) + private + FPairDelimiter, FItemDelimiter: Char; + function GetAsText: String; + procedure SetAsText(Input: String); + public + constructor Create; + procedure CleanToKeep; + property AsText: String read GetAsText write SetAsText; + end; + TBindParamComparer = class(TComparer<TBindParam>) + function Compare(constref Left, Right: TBindParam): Integer; override; + end; + + {// Query tabs and contained components + TQueryTab = class; + TResultTab = class(TObject) + Results: TDBQuery; + Grid: TVirtualStringTree; + FilterText: String; + private + FTabIndex: Integer; + public + constructor Create(AOwner: TQueryTab); + destructor Destroy; override; + property TabIndex: Integer read FTabIndex; + end; + TResultTabs = TObjectList<TResultTab>;} + {TQueryTab = class(TComponent) + const + IdentBackupFilename = 'BackupFilename'; + IdentFilename = 'Filename'; + IdentFileEncoding = 'FileEncoding'; + IdentCaption = 'Caption'; + IdentPid = 'pid'; + IdentEditorHeight = 'EditorHeight'; + IdentHelpersWidth = 'HelpersWidth'; + IdentBindParams = 'BindParams'; + IdentEditorTopLine = 'EditorTopLine'; + IdentTabFocused = 'TabFocused'; + HelperNodeColumns = 0; + HelperNodeFunctions = 1; + HelperNodeKeywords = 2; + HelperNodeSnippets = 3; + HelperNodeHistory = 4; + HelperNodeProfile = 5; + HelperNodeBinding = 6; + private + FMemoFilename: String; + FQueryRunning: Boolean; + FLastChange: TDateTime; + FDirectoryWatchNotficationRunning: Boolean; + FErrorLine: Integer; + FFileEncoding: String; + procedure SetMemoFilename(Value: String); + procedure SetQueryRunning(Value: Boolean); + procedure TimerLastChangeOnTimer(Sender: TObject); + procedure TimerStatusUpdateOnTimer(Sender: TObject); + function GetBindParamsActivated: Boolean; + procedure SetBindParamsActivated(Value: Boolean); + procedure SetErrorLine(Value: Integer); + public + Number: Integer; + Uid: String; + ExecutionThread: TQueryThread; + CloseButton: TSpeedButton; + pnlMemo: TPanel; + Memo: TSynMemo; + pnlHelpers: TPanel; + filterHelpers: TButtonedEdit; + treeHelpers: TVirtualStringTree; + MemoFileRenamed: Boolean; + MemoLineBreaks: TLineBreaks; + DirectoryWatch: TDirectoryWatch; + MemofileModifiedTimer: TTimer; + LastSaveTime: Cardinal; + spltHelpers: TSplitter; + spltQuery: TSplitter; + tabsetQuery: TTabSet; + TabSheet: TTabSheet; + ResultTabs: TResultTabs; + DoProfile: Boolean; + QueryProfile: TDBQuery; + ProfileTime, MaxProfileTime: Extended; + LeftOffsetInMemo: Integer; + HistoryDays: TStringList; + ListBindParams: TListBindParam; + TimerLastChange: TTimer; + TimerStatusUpdate: TTimer; + function GetActiveResultTab: TResultTab; + procedure DirectoryWatchNotify(const Sender: TObject; const Action: TWatchAction; const FileName: string); + procedure DirectoryWatchErrorHandler(const Sender: TObject; const ErrorCode: Integer; const ErrorMessage: string); + procedure MemofileModifiedTimerNotify(Sender: TObject); + function LoadContents(Filepath: String; ReplaceContent: Boolean; Encoding: TEncoding): Boolean; + property BindParamsActivated: Boolean read GetBindParamsActivated write SetBindParamsActivated; + procedure SaveContents(Filename: String; OnlySelection: Boolean); + procedure BackupUnsavedContent; + property ActiveResultTab: TResultTab read GetActiveResultTab; + property MemoFilename: String read FMemoFilename write SetMemoFilename; + function MemoBackupFilename: String; + property QueryRunning: Boolean read FQueryRunning write SetQueryRunning; + constructor Create(AOwner: TComponent); override; + destructor Destroy; override; + class function GenerateUid: String; + property ErrorLine: Integer read FErrorLine write SetErrorLine; + property FileEncoding: String read FFileEncoding write FFileEncoding; + end; + TQueryTabList = class(TObjectList<TQueryTab>) + public + function ActiveTab: TQueryTab; + function ActiveMemo: TSynMemo; + function ActiveHelpersTree: TVirtualStringTree; + function HasActiveTab: Boolean; + function TabByNumber(Number: Integer): TQueryTab; + function TabByControl(Control: TWinControl): TQueryTab; + end;} + + {TQueryHistoryItem = class(TObject) + Time: TDateTime; + Database: String; + SQL: String; + Duration: Cardinal; + RegValue: Integer; + end; + TQueryHistory = class(TObjectList<TQueryHistoryItem>) + private + FMaxDuration: Cardinal; + public + constructor Create(SessionPath: String); + property MaxDuration: Cardinal read FMaxDuration; + end; + TQueryHistoryItemComparer = class(TComparer<TQueryHistoryItem>) + function Compare(const Left, Right: TQueryHistoryItem): Integer; override; + end;} + + {ITaskbarList = interface(IUnknown) + [SID_ITaskbarList] + function HrInit: HRESULT; stdcall; + function AddTab(hwnd: HWND): HRESULT; stdcall; + function DeleteTab(hwnd: HWND): HRESULT; stdcall; + function ActivateTab(hwnd: HWND): HRESULT; stdcall; + function SetActiveAlt(hwnd: HWND): HRESULT; stdcall; + end; + ITaskbarList2 = interface(ITaskbarList) + [SID_ITaskbarList2] + function MarkFullscreenWindow(hwnd: HWND; fFullscreen: BOOL): HRESULT; stdcall; + end; + ITaskbarList3 = interface(ITaskbarList2) + [SID_ITaskbarList3] + function SetProgressValue(hwnd: HWND; ullCompleted: ULONGLONG; ullTotal: ULONGLONG): HRESULT; stdcall; + function SetProgressState(hwnd: HWND; tbpFlags: Integer): HRESULT; stdcall; + function RegisterTab(hwndTab: HWND; hwndMDI: HWND): HRESULT; stdcall; + function UnregisterTab(hwndTab: HWND): HRESULT; stdcall; + function SetTabOrder(hwndTab: HWND; hwndInsertBefore: HWND): HRESULT; stdcall; + function SetTabActive(hwndTab: HWND; hwndMDI: HWND; tbatFlags: Integer): HRESULT; stdcall; + function ThumbBarAddButtons(hwnd: HWND; cButtons: UINT; pButton: PThumbButton): HRESULT; stdcall; + function ThumbBarUpdateButtons(hwnd: HWND; cButtons: UINT; pButton: PThumbButton): HRESULT; stdcall; + function ThumbBarSetImageList(hwnd: HWND; himl: HIMAGELIST): HRESULT; stdcall; + function SetOverlayIcon(hwnd: HWND; hIcon: HICON; pszDescription: LPCWSTR): HRESULT; stdcall; + function SetThumbnailTooltip(hwnd: HWND; pszTip: LPCWSTR): HRESULT; stdcall; + function SetThumbnailClip(hwnd: HWND; var prcClip: TRect): HRESULT; stdcall; + end;} + + TMainForm = class(TForm) + actSessionManager: TAction; + ActionList1: TActionList; + actExitApplication: TAction; + ImageListIcons8: TImageList; + DBtree: TLazVirtualStringTree; + MainMenu1: TMainMenu; + MenuItem1: TMenuItem; + MenuItem2: TMenuItem; + MenuItem3: TMenuItem; + MenuItem4: TMenuItem; + MenuItem5: TMenuItem; + MenuItem6: TMenuItem; + MenuItem7: TMenuItem; + MenuItem8: TMenuItem; + PageControlMain: TPageControl; + Panel1: TPanel; + Panel2: TPanel; + pnlPreview: TPanel; + pnlFilterVT: TPanel; + pnlLeft: TPanel; + spltPreview: TSplitter; + spltDBtree: TSplitter; + spltTopBottom: TSplitter; + StatusBar: TStatusBar; + SynMemoSQLLog: TSynEdit; + SynSQLSynUsed: TSynSQLSyn; + tabHost: TTabSheet; + tabDatabase: TTabSheet; + tabTable: TTabSheet; + tabData: TTabSheet; + tabQuery: TTabSheet; + ToolBarTree: TToolBar; + ToolBarMainButtons: TToolBar; + ToolButton1: TToolButton; + //procedure actCreateDBObjectExecute(Sender: TObject); + //procedure menuConnectionsPopup(Sender: TObject); + procedure actExitApplicationExecute(Sender: TObject); + //procedure WMCopyData(var Msg: TWMCopyData); message WM_COPYDATA; + //procedure CMStyleChanged(var Msg: TMessage); message CM_STYLECHANGED; + //procedure FormDestroy(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure AfterFormCreate; + //procedure FormShow(Sender: TObject); + //procedure FormResize(Sender: TObject); + //procedure AddEditorCommandMenu(const S: string); + //procedure EditorCommandOnClick(Sender: TObject); + //procedure actUserManagerExecute(Sender: TObject); + //procedure actAboutBoxExecute(Sender: TObject); + //procedure actApplyFilterExecute(Sender: TObject); + //procedure actClearEditorExecute(Sender: TObject); + //procedure actTableToolsExecute(Sender: TObject); + //procedure actPrintListExecute(Sender: TObject); + //procedure actCopyTableExecute(Sender: TObject); + //procedure ShowStatusMsg(Msg: String=''; PanelNr: Integer=6); + //procedure actExecuteQueryExecute(Sender: TObject); + //procedure actCreateDatabaseExecute(Sender: TObject); + //procedure actDataCancelChangesExecute(Sender: TObject); + //procedure actExportDataExecute(Sender: TObject); + //procedure actDataPreviewExecute(Sender: TObject); + //procedure UpdatePreviewPanel; + //procedure actInsertFilesExecute(Sender: TObject); + //procedure actDataDeleteExecute(Sender: TObject); + //procedure actDataFirstExecute(Sender: TObject); + //procedure actDataInsertExecute(Sender: TObject); + //procedure actDataLastExecute(Sender: TObject); + //procedure actDataPostChangesExecute(Sender: TObject); + //procedure actDropObjectsExecute(Sender: TObject); + //procedure actEmptyTablesExecute(Sender: TObject); + //procedure actExportSettingsExecute(Sender: TObject); + //procedure actFlushExecute(Sender: TObject); + //procedure actImportCSVExecute(Sender: TObject); + //procedure actImportSettingsExecute(Sender: TObject); + //procedure actLoadSQLExecute(Sender: TObject); + //procedure actNewWindowExecute(Sender: TObject); + procedure actSessionManagerExecute(Sender: TObject); + //procedure actPreferencesExecute(Sender: TObject); + //procedure actQueryFindReplaceExecute(Sender: TObject); + //procedure actQueryStopOnErrorsExecute(Sender: TObject); + //procedure actQueryWordWrapExecute(Sender: TObject); + //procedure actHelpExecute(Sender: TObject); + //procedure actRefreshExecute(Sender: TObject); + //procedure actRemoveFilterExecute(Sender: TObject); + //procedure actSaveSQLExecute(Sender: TObject); + //procedure actSaveSQLAsExecute(Sender: TObject); + //procedure actSetDelimiterExecute(Sender: TObject); + //procedure actSQLhelpExecute(Sender: TObject); + //procedure actUpdateCheckExecute(Sender: TObject); + //procedure actWebbrowse(Sender: TObject); + //procedure popupQueryPopup(Sender: TObject); + //procedure btnDataClick(Sender: TObject); + //procedure ListTablesChange(Sender: TBaseVirtualTree; Node: PVirtualNode); + //procedure SynCompletionProposalAfterCodeCompletion(Sender: TObject; + // const Value: String; Shift: TShiftState; Index: Integer; EndToken: Char); + //procedure SynCompletionProposalCodeCompletion(Sender: TObject; + // var Value: String; Shift: TShiftState; Index: Integer; EndToken: Char); + //procedure SynCompletionProposalExecute(Kind: SynCompletionType; + // Sender: TObject; var CurrentInput: String; var x, y: Integer; + // var CanExecute: Boolean); + //procedure PageControlMainChange(Sender: TObject); + //procedure PageControlMainChanging(Sender: TObject; var AllowChange: Boolean); + //procedure PageControlHostChange(Sender: TObject); + //procedure ValidateControls(Sender: TObject); + //procedure ValidateQueryControls(Sender: TObject); + //procedure DataGridBeforePaint(Sender: TBaseVirtualTree; + // TargetCanvas: TCanvas); + procedure LogSQL(Msg: String; Category: TDBLogCategory=lcInfo; Connection: TDBConnection=nil); + //procedure KillProcess(Sender: TObject); + //procedure TimerHostUptimeTimer(Sender: TObject); + //procedure ListTablesNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; NewText: String); + //procedure TimerConnectedTimer(Sender: TObject); + //procedure QuickFilterClick(Sender: TObject); + //procedure AutoRefreshSetInterval(Sender: TObject); + //procedure AutoRefreshToggle(Sender: TObject); + //procedure SynMemoQueryDragOver(Sender, Source: TObject; X, Y: Integer; + // State: TDragState; var Accept: Boolean); + //procedure SynMemoQueryDragDrop(Sender, Source: TObject; X, Y: Integer); + //procedure SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer; AFiles: TUnicodeStrings); + //procedure popupHostPopup(Sender: TObject); + //procedure popupDBPopup(Sender: TObject); + //procedure popupDataGridPopup(Sender: TObject); + //procedure QFvaluesClick(Sender: TObject); + //procedure DataInsertValueClick(Sender: TObject); + //procedure InsertValue(Sender: TObject); + //procedure actDataSetNullExecute(Sender: TObject); + //procedure AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; out EditLink: IVTEditLink); + //procedure AnyGridEditCancelled(Sender: TBaseVirtualTree; Column: TColumnIndex); + //procedure AnyGridEdited(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: + // TColumnIndex); + //procedure AnyGridEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: + // TColumnIndex; var Allowed: Boolean); + //procedure AnyGridFocusChanging(Sender: TBaseVirtualTree; OldNode, NewNode: + // PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean); + //procedure AnyGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: + // TColumnIndex; TextType: TVSTTextType; var CellText: String); + //procedure DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); + //procedure AnyGridKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); + //procedure AnyGridMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; + // MousePos: TPoint; var Handled: Boolean); + //procedure AnyGridNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: + // TColumnIndex; NewText: String); + //procedure AnyGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: + // TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); + //procedure menuDeleteSnippetClick(Sender: TObject); + //procedure menuExploreClick(Sender: TObject); + //procedure menuInsertAtCursorClick(Sender: TObject); + //procedure menuLoadSnippetClick(Sender: TObject); + //procedure AnyGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); + //procedure AnyGridCompareNodes(Sender: TBaseVirtualTree; Node1, Node2: + // PVirtualNode; Column: TColumnIndex; var Result: Integer); + //procedure AnyGridHeaderDraggedOut(Sender: TVTHeader; Column: TColumnIndex; + // DropPosition: TPoint); + //procedure AnyGridIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; const SearchText: String; + // var Result: Integer); + //procedure SetMainTab(Page: TTabSheet); + //procedure DBtreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex); + //procedure DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var + // ImageIndex: TImageIndex); + //procedure DBtreeGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: + // Integer); + //procedure DBtreeGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: + // TColumnIndex; TextType: TVSTTextType; var CellText: String); + //procedure DBtreeInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var + // ChildCount: Cardinal); + //procedure DBtreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node: + // PVirtualNode; var InitialStates: TVirtualNodeInitStates); + //procedure DBtreePaintText(Sender: TBaseVirtualTree; const TargetCanvas: + // TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); + //procedure editFilterSearchChange(Sender: TObject); + //procedure editFilterSearchEnter(Sender: TObject); + //procedure editFilterSearchExit(Sender: TObject); + //procedure menuLogToFileClick(Sender: TObject); + //procedure menuOpenLogFolderClick(Sender: TObject); + //procedure AnyGridGetHint(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; var LineBreakStyle: TVTTooltipLineBreakStyle; var + // HintText: String); + //procedure ListTablesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + // Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + // var ContentRect: TRect); + //procedure ListProcessesFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); + //procedure editFilterVTChange(Sender: TObject); + //procedure ListVariablesDblClick(Sender: TObject); + //procedure menuEditVariableClick(Sender: TObject); + //procedure menuTreeCollapseAllClick(Sender: TObject); + //procedure menuTreeExpandAllClick(Sender: TObject); + //procedure SynMemoFilterStatusChange(Sender: TObject; Changes: TSynStatusChanges); + //procedure AnyGridAfterCellPaint(Sender: TBaseVirtualTree; + // TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + // CellRect: TRect); + //procedure menuShowSizeColumnClick(Sender: TObject); + //procedure AnyGridBeforeCellPaint(Sender: TBaseVirtualTree; + // TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + // CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); + //procedure AnyGridMouseUp(Sender: TObject; Button: TMouseButton; + // Shift: TShiftState; X, Y: Integer); + //procedure MainMenuFileClick(Sender: TObject); + //procedure HostListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); + //procedure HostListGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); + //procedure HostListBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); + //procedure HostListBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + // Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + // var ContentRect: TRect); + //procedure ListTablesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); + //procedure ListTablesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; Kind: TVTImageKind; + // Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); + //procedure ListTablesGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); + //procedure ListTablesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); + //procedure ListTablesInitNode(Sender: TBaseVirtualTree; ParentNode, + // Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); + //procedure AnyGridAfterPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); + //procedure actFollowForeignKeyExecute(Sender: TObject); + //procedure actCopyOrCutExecute(Sender: TObject); + //procedure actPasteExecute(Sender: TObject); + //procedure actSelectAllExecute(Sender: TObject); + //procedure EnumerateRecentFilters; + //procedure LoadRecentFilter(Sender: TObject); + //procedure ListTablesEditing(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; var Allowed: Boolean); + //procedure DBtreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode); + //procedure ListTablesDblClick(Sender: TObject); + //procedure panelTopDblClick(Sender: TObject); + //procedure PageControlMainMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); + //procedure actNewQueryTabExecute(Sender: TObject); + //procedure actCloseQueryTabExecute(Sender: TObject); + //procedure menuCloseQueryTabClick(Sender: TObject); + //procedure CloseQueryTab(PageIndex: Integer); + //procedure CloseButtonOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); + //procedure CloseButtonOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); + //function GetMainTabAt(X, Y: Integer): Integer; + //procedure FixQueryTabCloseButtons; + //function GetOrCreateEmptyQueryTab(DoFocus: Boolean): TQueryTab; + //function ActiveSynMemo(AcceptReadOnlyMemo: Boolean): TSynMemo; + //function IsQueryTab(PageIndex: Integer; IncludeFixed: Boolean): Boolean; + //procedure popupMainTabsPopup(Sender: TObject); + //procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); + //procedure actFilterPanelExecute(Sender: TObject); + //procedure TimerFilterVTTimer(Sender: TObject); + //procedure PageControlMainContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); + //procedure menuQueryHelpersGenerateStatementClick(Sender: TObject); + //procedure actSelectInverseExecute(Sender: TObject); + //procedure FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; + // var Handled: Boolean); + //procedure actDataResetSortingExecute(Sender: TObject); + //procedure actReformatSQLExecute(Sender: TObject); + //procedure DBtreeFocusChanging(Sender: TBaseVirtualTree; OldNode, + // NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; + // var Allowed: Boolean); + //procedure actBlobAsTextExecute(Sender: TObject); + //procedure SynMemoQueryReplaceText(Sender: TObject; const ASearch, + // AReplace: string; Line, Column: Integer; var Action: TSynReplaceAction); + //procedure SynMemoQueryPaintTransient(Sender: TObject; Canvas: TCanvas; + // TransientType: TTransientType); + //procedure actQueryFindAgainExecute(Sender: TObject); + //procedure AnyGridScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer); + //procedure lblExplainProcessClick(Sender: TObject); + //procedure actDataShowNextExecute(Sender: TObject); + //procedure actDataShowAllExecute(Sender: TObject); + //procedure AnyGridInitNode(Sender: TBaseVirtualTree; ParentNode, + // Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); + //procedure AnyGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex); + //procedure ListTablesKeyPress(Sender: TObject; var Key: Char); + //procedure DBtreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); + //procedure ListDatabasesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); + //procedure ListDatabasesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); + //procedure ListDatabasesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; + // var InitialStates: TVirtualNodeInitStates); + //procedure ListDatabasesGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); + //procedure menuFetchDBitemsClick(Sender: TObject); + //procedure ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); + //procedure ListDatabasesDblClick(Sender: TObject); + //procedure actRunRoutinesExecute(Sender: TObject); + //procedure AnyGridGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); + //procedure tabsetQueryClick(Sender: TObject); + //procedure tabsetQueryGetImageIndex(Sender: TObject; TabIndex: Integer; var ImageIndex: Integer); + //procedure tabsetQueryMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); + //procedure tabsetQueryMouseLeave(Sender: TObject); + //procedure StatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; const Rect: TRect); + //procedure StatusBarMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); + //procedure StatusBarMouseLeave(Sender: TObject); + //procedure AnyGridStartOperation(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind); + //procedure AnyGridEndOperation(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind); + //procedure actDataPreviewUpdate(Sender: TObject); + //procedure spltPreviewMoved(Sender: TObject); + //procedure actDataSaveBlobToFileExecute(Sender: TObject); + //procedure DataGridColumnResize(Sender: TVTHeader; Column: TColumnIndex); + //procedure treeQueryHelpersGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); + //procedure treeQueryHelpersInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; + // var InitialStates: TVirtualNodeInitStates); + //procedure treeQueryHelpersInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; + // var ChildCount: Cardinal); + //procedure treeQueryHelpersGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + // Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); + //procedure treeQueryHelpersBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + // Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + // var ContentRect: TRect); + //procedure treeQueryHelpersDblClick(Sender: TObject); + //procedure treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); + //procedure treeQueryHelpersFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); + //procedure treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; + // Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); + //procedure treeQueryHelpersFocusChanging(Sender: TBaseVirtualTree; OldNode, + // NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean); + //procedure treeQueryHelpersResize(Sender: TObject); + //procedure ApplicationEvents1Deactivate(Sender: TObject); + //procedure actDisconnectExecute(Sender: TObject); + //procedure menuEditObjectClick(Sender: TObject); + //procedure Copylinetonewquerytab1Click(Sender: TObject); + //procedure actLogHorizontalScrollbarExecute(Sender: TObject); + //procedure actBatchInOneGoExecute(Sender: TObject); + //function GetFocusedObjects(Sender: TObject; NodeTypes: TListNodeTypes): TDBObjectList; + //function DBTreeClicked(Sender: TObject): Boolean; + //procedure actCancelOperationExecute(Sender: TObject); + //procedure AnyGridChange(Sender: TBaseVirtualTree; Node: PVirtualNode); + //procedure actToggleCommentExecute(Sender: TObject); + //procedure DBtreeBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + // Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + // var ContentRect: TRect); + //procedure actLaunchCommandlineExecute(Sender: TObject); + //procedure menuClearQueryHistoryClick(Sender: TObject); + //procedure actGridEditFunctionExecute(Sender: TObject); + //procedure ListVariablesPaintText(Sender: TBaseVirtualTree; + // const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + // TextType: TVSTTextType); + //procedure DBtreeExpanding(Sender: TBaseVirtualTree; Node: PVirtualNode; + // var Allowed: Boolean); + //procedure actGroupObjectsExecute(Sender: TObject); + //procedure popupSqlLogPopup(Sender: TObject); + //procedure menuAutoExpandClick(Sender: TObject); + //procedure pnlLeftResize(Sender: TObject); + //procedure editDatabaseTableFilterChange(Sender: TObject); + //procedure editDatabaseTableFilterLeftButtonClick(Sender: TObject); + //procedure editDatabaseTableFilterMenuClick(Sender: TObject); + //procedure editDatabaseTableFilterExit(Sender: TObject); + //procedure menuClearDataTabFilterClick(Sender: TObject); + //procedure actUnixTimestampColumnExecute(Sender: TObject); + //procedure PopupQueryLoadPopup(Sender: TObject); + //procedure DonateClick(Sender: TObject); + //procedure DBtreeExpanded(Sender: TBaseVirtualTree; Node: PVirtualNode); + //procedure ApplicationDeActivate(Sender: TObject); + //procedure ApplicationShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo); + //procedure DBtreeAfterCellPaint(Sender: TBaseVirtualTree; + // TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + // CellRect: TRect); + //procedure actFavoriteObjectsOnlyExecute(Sender: TObject); + //procedure DBtreeMouseUp(Sender: TObject; Button: TMouseButton; + // Shift: TShiftState; X, Y: Integer); + //procedure actFullRefreshExecute(Sender: TObject); + //procedure treeQueryHelpersEditing(Sender: TBaseVirtualTree; + // Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); + //procedure treeQueryHelpersCreateEditor(Sender: TBaseVirtualTree; + // Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink); + //procedure treeQueryHelpersNodeClick(Sender: TBaseVirtualTree; + // const HitInfo: THitInfo); + //procedure treeQueryHelpersNewText(Sender: TBaseVirtualTree; + // Node: PVirtualNode; Column: TColumnIndex; NewText: string); + //procedure treeQueryHelpersChecking(Sender: TBaseVirtualTree; + // Node: PVirtualNode; var NewState: TCheckState; var Allowed: Boolean); + //procedure actPreviousResultExecute(Sender: TObject); + //procedure actNextResultExecute(Sender: TObject); + //procedure actSaveSynMemoToTextfileExecute(Sender: TObject); + //procedure ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); + //procedure buttonedEditClear(Sender: TObject); + //procedure menuDoubleClickInsertsNodeTextClick(Sender: TObject); + //procedure DBtreeDblClick(Sender: TObject); + //procedure editDatabaseTableFilterKeyPress(Sender: TObject; var Key: Char); + procedure actGotoDbTreeExecute(Sender: TObject); + //procedure actGotoFilterExecute(Sender: TObject); + //procedure actGotoTabNumberExecute(Sender: TObject); + //procedure StatusBarClick(Sender: TObject); + //procedure AnySynMemoMouseWheel(Sender: TObject; Shift: TShiftState; + // WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); + //procedure SynMemoQueryKeyPress(Sender: TObject; var Key: Char); + //procedure filterQueryHelpersChange(Sender: TObject); + //procedure TimerStoreTabsTimer(Sender: TObject); + //procedure actGoToQueryResultsExecute(Sender: TObject); + //procedure actGoToDataMultiFilterExecute(Sender: TObject); + //procedure actDataOpenUrlExecute(Sender: TObject); + //procedure ApplicationEvents1ShortCut(var Msg: TWMKey; var Handled: Boolean); + //procedure actDetachDatabaseExecute(Sender: TObject); + //procedure actAttachDatabaseExecute(Sender: TObject); + //procedure actSynEditCompletionProposeExecute(Sender: TObject); + //procedure AnyGridHeaderDrawQueryElements(Sender: TVTHeader; + // var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); + //procedure AnyGridAdvancedHeaderDraw(Sender: TVTHeader; + // var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); + //procedure SynMemoQueryScanForFoldRanges(Sender: TObject; + // FoldRanges: TSynFoldRanges; LinesToScan: TStrings; FromLine, + // ToLine: Integer); + //procedure actCodeFoldingExecute(Sender: TObject); + //procedure actCodeFoldingStartRegionExecute(Sender: TObject); + //procedure actCodeFoldingEndRegionExecute(Sender: TObject); + //procedure actCodeFoldingFoldSelectionExecute(Sender: TObject); + //procedure actConnectionPropertiesExecute(Sender: TObject); + //procedure actRenameQueryTabExecute(Sender: TObject); + //procedure menuRenameQueryTabClick(Sender: TObject); + //procedure SynMemoQueryStatusChange(Sender: TObject; Changes: TSynStatusChanges); + //procedure actCloseAllQueryTabsExecute(Sender: TObject); + //procedure menuCloseRightQueryTabsClick(Sender: TObject); + //procedure popupFilterPopup(Sender: TObject); + //procedure actSynMoveDownExecute(Sender: TObject); + //procedure actSynMoveUpExecute(Sender: TObject); + //procedure actCopyTabsToSpacesExecute(Sender: TObject); + //procedure actCopyUpdate(Sender: TObject); + //procedure FormBeforeMonitorDpiChanged(Sender: TObject; OldDPI, + // NewDPI: Integer); + //procedure menuToggleAllClick(Sender: TObject); + //procedure FormAfterMonitorDpiChanged(Sender: TObject; OldDPI, + // NewDPI: Integer); + //procedure menuCloseTabOnDblClickClick(Sender: TObject); + //procedure TimerRefreshTimer(Sender: TObject); + //procedure SynCompletionProposalChange(Sender: TObject; AIndex: Integer); + //procedure SynMemoQuerySpecialLineColors(Sender: TObject; Line: Integer; + // var Special: Boolean; var FG, BG: TColor); + //procedure SynMemoSQLLogSpecialLineColors(Sender: TObject; Line: Integer; + // var Special: Boolean; var FG, BG: TColor); + //procedure actSequalSuggestExecute(Sender: TObject); + //procedure menuQueryExactRowCountClick(Sender: TObject); + //procedure menuCloseTabOnMiddleClickClick(Sender: TObject); + //procedure TimerCloseTabByButtonTimer(Sender: TObject); + //procedure menuTabsInMultipleLinesClick(Sender: TObject); + //procedure actResetPanelDimensionsExecute(Sender: TObject); + //procedure menuAlwaysGenerateFilterClick(Sender: TObject); + //procedure SynMemoQueryTokenHint(Sender: TObject; Coords: TBufferCoord; + // const Token: string; TokenType: Integer; Attri: TSynHighlighterAttributes; + // var HintText: string); + //procedure actCopyGridNodesExecute(Sender: TObject); + private + // Executable file details + FAppVerMajor: Integer; + FAppVerMinor: Integer; + FAppVerRelease: Integer; + FAppVerRevision: Integer; + FAppVersion: String; + + FLastHintMousepos: TPoint; + FLastHintControlIndex: Integer; + FDelimiter: String; + FLogToFile: Boolean; + FFileNameSessionLog: String; + FFileHandleSessionLog: Textfile; + FLastMouseUpOnPageControl: Cardinal; + FLastTabNumberOnMouseUp: Integer; + FLastMouseDownCloseButton: TObject; + //FJumpList: TJumpList; + //// Filter text per tab for filter panel + //FFilterTextDatabases, + //FFilterTextEditor, + //FFilterTextVariables, + //FFilterTextStatus, + //FFilterTextProcessList, + //FFilterTextCommandStats, + //FFilterTextDatabase, + //FFilterTextData: String; + //FTreeRefreshInProgress: Boolean; + //FRefreshActionDisabledAt: Cardinal; + //FDataGridColumnWidthsCustomized: Boolean; + //FDataGridLastClickedColumnHeader: Integer; + //FDataGridLastClickedColumnLeftPos: Integer; + //FDataGridSortItems: TSortItems; + //FSnippetFilenames: TStringList; + //FConnections: TDBConnectionList; + //FTreeClickHistory: TNodeArray; + //FOperationTicker: Cardinal; + //FOperatingGrid: TBaseVirtualTree; + //FActiveDbObj: TDBObject; + //FActiveObjectGroup: TListNodeType; + //FBtnAddTab: TSpeedButton; + //FDBObjectsMaxSize: Int64; + //FDBObjectsMaxRows: Int64; + //FSearchReplaceDialog: TfrmSearchReplace; + //FCreateDatabaseDialog: TCreateDatabaseForm; + //FTableToolsDialog: TfrmTableTools; + //FGridEditFunctionMode: Boolean; + //FClipboardHasNull: Boolean; + FTimeZoneOffset: Integer; + //FGridCopying: Boolean; + //FGridPasting: Boolean; + //FHasDonatedDatabaseCheck: TThreeStateBoolean; + //FFocusedTables: TDBObjectList; + FLastCaptionChange: Cardinal; + //FListTablesSorted: Boolean; + FLastPortableSettingsSave: Cardinal; + FLastAppSettingsWrites: Integer; + FFormatSettings: TFormatSettings; + FDefaultHintFontName: String; + //FActionList1DefaultCaptions: TStringList; + //FActionList1DefaultHints: TStringList; + //FEditorCommandStrings: TStringList; + //FLastSelWordInEditor: String; + //FMatchingBraceForegroundColor: TColor; + //FMatchingBraceBackgroundColor: TColor; + //FSynEditInOnPaintTransient: Boolean; + //FExactRowCountMode: Boolean; + ////FHelpData: TSimpleKeyValuePairs; + // + //// Host subtabs backend structures + //FHostListResults: TDBQueryList; + //FStatusServerUptime: Integer; + //FProcessListMaxTime: Int64; + //FCommandStatsQueryCount: Int64; + //FCommandStatsServerUptime: Integer; + //FVariableNames, FSessionVars, FGlobalVars: TStringList; + // + procedure SetDelimiter(Value: String); + //procedure DisplayRowCountStats(Sender: TBaseVirtualTree); + //procedure insertFunction(Sender: TObject); + //function GetActiveConnection: TDBConnection; + //function GetActiveDatabase: String; + //function GetCurrentQuery(Tab: TQueryTab): String; + //procedure SetActiveDatabase(db: String; Connection: TDBConnection); + //procedure SetActiveDBObj(Obj: TDBObject); + //procedure ToggleFilterPanel(ForceVisible: Boolean = False); + //procedure EnableDataTab(Enable: Boolean); + //procedure AutoCalcColWidth(Tree: TVirtualStringTree; Column: TColumnIndex); + //procedure PlaceObjectEditor(Obj: TDBObject); + //procedure SetTabCaption(PageIndex: Integer; Text: String); + //function ConfirmTabClose(PageIndex: Integer; AppIsClosing: Boolean): Boolean; + //function ConfirmTabClear(PageIndex: Integer; AppIsClosing: Boolean): Boolean; + //procedure UpdateFilterPanel(Sender: TObject); + //procedure ConnectionReady(Connection: TDBConnection; Database: String); + //procedure DatabaseChanged(Connection: TDBConnection; Database: String); + //procedure ObjectnamesChanged(Connection: TDBConnection; Database: String); + //procedure UpdateLineCharPanel; + //procedure SetSnippetFilenames; + //function TreeClickHistoryPrevious(MayBeNil: Boolean=False): PVirtualNode; + //procedure OperationRunning(Runs: Boolean); + //function RunQueryFiles(Filenames: TStrings; Encoding: TEncoding; ForceRun: Boolean): Boolean; + //function RunQueryFile(Filename: String; Encoding: TEncoding; Conn: TDBConnection; + // ProgressDialog: IProgressDialog; FilesizeSum: Int64; var CurrentPosition: Int64): Boolean; + //procedure SetLogToFile(Value: Boolean); + //procedure StoreLastSessions; + //function HandleUnixTimestampColumn(Sender: TBaseVirtualTree; Column: TColumnIndex): Boolean; + //function InitTabsIniFile: TIniFile; + //procedure StoreTabs; + //function RestoreTabs: Boolean; + //procedure SetHintFontByControl(Control: TWinControl=nil); + public + //QueryTabs: TQueryTabList; + //ActiveObjectEditor: TDBObjectEditor; + //FileEncodings: TStringList; + //ImportSettingsDone: Boolean; + // + //// Data grid related stuff + //DataGridHiddenColumns: TStringList; + //DataGridWantedRowCount: Int64; + //DataGridTable: TDBObject; + //DataGridFocusedCell: TStringList; + //DataGridFocusedNodeIndex: Int64; + //DataGridFocusedColumnName: String; + //DataGridResult: TDBQuery; + //DataGridFullRowMode: Boolean; + //DataLocalNumberFormat: Boolean; + //SelectedTableColumns: TTableColumnList; + //SelectedTableKeys: TTableKeyList; + //SelectedTableForeignKeys: TForeignKeyList; + //SelectedTableTimestampColumns: TStringList; + //FilterPanelManuallyOpened: Boolean; + // + //// Task button interface + //TaskbarList: ITaskbarList; + //TaskbarList2: ITaskbarList2; + //TaskbarList3: ITaskbarList3; + //TaskbarList4: ITaskbarList4; + // + property AppVerRevision: Integer read FAppVerRevision; + property AppVersion: String read FAppVersion; + //property Connections: TDBConnectionList read FConnections; + property Delimiter: String read FDelimiter write SetDelimiter; + //property FocusedTables: TDBObjectList read FFocusedTables; + //function GetAlternatingRowBackground(Node: PVirtualNode): TColor; + //procedure PaintAlternatingRowBackground(TargetCanvas: TCanvas; Node: PVirtualNode; CellRect: TRect); + //procedure PaintColorBar(Value, Max: Extended; TargetCanvas: TCanvas; CellRect: TRect); + //procedure CallSQLHelpWithKeyword( keyword: String ); + //procedure AddOrRemoveFromQueryLoadHistory(Filename: String; AddIt: Boolean; CheckIfFileExists: Boolean); + //procedure popupQueryLoadClick( sender: TObject ); + //procedure PopupQueryLoadRemoveAbsentFiles(Sender: TObject); + //procedure PopupQueryLoadRemoveAllFiles(Sender: TObject); + //procedure SessionConnect(Sender: TObject); + //function InitConnection(Params: TConnectionParameters; ActivateMe: Boolean; var Connection: TDBConnection): Boolean; + //procedure ConnectionsNotify(Sender: TObject; const Item: TDBConnection; Action: TCollectionNotification); + //function ActiveGrid: TVirtualStringTree; + //function GridResult(Grid: TBaseVirtualTree): TDBQuery; + //property ActiveConnection: TDBConnection read GetActiveConnection; + //property ActiveDatabase: String read GetActiveDatabase; + //property ActiveDbObj: TDBObject read FActiveDbObj write SetActiveDBObj; + property LogToFile: Boolean read FLogToFile write FLogToFile; //SetLogToFile; + //procedure RefreshTree(FocusNewObject: TDBObject=nil); + //function GetRootNode(Tree: TBaseVirtualTree; Connection: TDBConnection): PVirtualNode; + //function FindDBObjectNode(Tree: TBaseVirtualTree; Obj: TDBObject): PVirtualNode; + //function FindDBNode(Tree: TBaseVirtualTree; Connection: TDBConnection; db: String): PVirtualNode; + //procedure CalcNullColors; + //procedure HandleDataGridAttributes(RefreshingData: Boolean); + //function GetRegKeyTable: String; + //procedure UpdateEditorTab; + //procedure SetWindowCaption; + //procedure DefaultHandler(var Message); override; + //procedure SetupSynEditors; overload; + //procedure SetupSynEditors(BaseForm: TComponent); overload; + //procedure SetupSynEditor(Editor: TSynMemo); + //function AnyGridEnsureFullRow(Grid: TVirtualStringTree; Node: PVirtualNode): Boolean; + //procedure DataGridEnsureFullRows(Grid: TVirtualStringTree; SelectedOnly: Boolean); + //property DataGridSortItems: TSortItems read FDataGridSortItems write FDataGridSortItems; + //function GetEncodingByName(Name: String): TEncoding; + //function GetEncodingName(Encoding: TEncoding): String; + //function GetCharsetByEncoding(Encoding: TEncoding): String; + //procedure RefreshHelperNode(NodeIndex: Cardinal); + //procedure BeforeQueryExecution(Thread: TQueryThread); + //procedure AfterQueryExecution(Thread: TQueryThread); + //procedure FinishedQueryExecution(Thread: TQueryThread); + //procedure EnableProgress(MaxValue: Integer); + //procedure DisableProgress; + //procedure SetProgressPosition(Value: Integer); + //procedure ProgressStep; + //procedure SetProgressState(State: TProgressbarState); + //procedure TaskDialogHyperLinkClicked(Sender: TObject); + //function HasDonated(ForceCheck: Boolean): TThreeStateBoolean; + //procedure ApplyVTFilter(FromTimer: Boolean); + //procedure ApplyFontToGrids; + //procedure PrepareImageList; + //property ActionList1DefaultCaptions: TStringList read FActionList1DefaultCaptions; + //property ActionList1DefaultHints: TStringList read FActionList1DefaultHints; + //function SelectedTableFocusedColumn: TTableColumn; + //property FormatSettings: TFormatSettings read FFormatSettings; + //property MatchingBraceForegroundColor: TColor read FMatchingBraceForegroundColor write FMatchingBraceForegroundColor; + //property MatchingBraceBackgroundColor: TColor read FMatchingBraceBackgroundColor write FMatchingBraceBackgroundColor; + end; + +var + MainForm: TMainForm; + SecondInstMsgId: Cardinal = 0; + SysLanguage: String; + MainFormCreated: Boolean = False; + MainFormAfterCreateDone: Boolean = False; + PostponedLogItems: TDBLogItems; + +const + CheckedStates = [csCheckedNormal, csCheckedPressed, csMixedNormal, csMixedPressed]; + ErrorLineForeground: TColor = $00000000; + ErrorLineBackground: TColor = $00D2B7FF; + WarningLineForeground: TColor = $00000000; + WarningLineBackground: TColor = $00B7CDFF; + NoteLineForeground: TColor = $00000000; + NoteLineBackground: TColor = $00D3F7FF; + InfoLineForeground: TColor = $00000000; + InfoLineBackground: TColor = $00C6FFEC; + +{$I const.inc} + + +implementation + +uses + FileInfo, winpeimagereader, elfreader, machoreader, apphelpers; + +{$R *.lfm} + +{ TMainForm } + + +{procedure TMainForm.ShowStatusMsg(Msg: String=''; PanelNr: Integer=6); +var + PanelRect: TRect; +begin + // Show message in some statusbar panel + if (PanelNr = 6) and (Msg = '') then + Msg := _(SIdle); + if Msg <> StatusBar.Panels[PanelNr].Text then begin + StatusBar.Panels[PanelNr].Text := Msg; + if (PanelNr = 6) and (not IsWine) then begin + // Immediately repaint this special panel, as it holds critical update messages, + // while avoiding StatusBar.Repaint which refreshes all panels + SendMessage(StatusBar.Handle, SB_GETRECT, PanelNr, Integer(@PanelRect)); + StatusBar.OnDrawPanel(StatusBar, StatusBar.Panels[PanelNr], PanelRect); + InvalidateRect(StatusBar.Handle, PanelRect, False); + // Alternatives: + //RedrawWindow(StatusBar.Handle, @PanelRect, 0, RDW_UPDATENOW); + //UpdateWindow(StatusBar.Handle); + //StatusBar.Repaint; + end; + end; +end;} + + +{procedure TMainForm.StatusBarClick(Sender: TObject); +var + Click: TPoint; + i: Integer; + PanelRect: TRect; +begin + // Handle click events on specific statusbar panels + // Prevent SendMessage on Wine + if IsWine then + Exit; + Click := StatusBar.ScreenToClient(Mouse.CursorPos); + for i:=0 to StatusBar.Panels.Count-1 do begin + SendMessage(StatusBar.Handle, SB_GETRECT, i, Integer(@PanelRect)); + if PtInRect(PanelRect, Click) then begin + // We found the clicked panel + case i of + 3: actConnectionProperties.Execute; + end; + Break; + end; + + end; + +end;} + + +{procedure TMainForm.StatusBarClick(Sender: TObject); +var + Click: TPoint; + i: Integer; + PanelRect: TRect; +begin + // Handle click events on specific statusbar panels + // Prevent SendMessage on Wine + if IsWine then + Exit; + Click := StatusBar.ScreenToClient(Mouse.CursorPos); + for i:=0 to StatusBar.Panels.Count-1 do begin + SendMessage(StatusBar.Handle, SB_GETRECT, i, Integer(@PanelRect)); + if PtInRect(PanelRect, Click) then begin + // We found the clicked panel + case i of + 3: actConnectionProperties.Execute; + end; + Break; + end; + + end; + +end;} + +{procedure TMainForm.StatusBarDrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; + const Rect: TRect); +var + PanelRect: TRect; + ImageIndex: Integer; + Conn: TDBConnection; +begin + // Refresh one status bar panel, probably with icon + ImageIndex := -1; + case Panel.Index of + 2: ImageIndex := 149; + 3: begin + Conn := ActiveConnection; + if Conn <> nil then + ImageIndex := Conn.Parameters.ImageIndex; + end; + 5: ImageIndex := 190; + 6: begin + if Panel.Text = _(SIdle) then + ImageIndex := 151 // Green dot + else + ImageIndex := 150; // Hourglass + end; + end; + PanelRect := Rect; + StatusBar.Canvas.FillRect(PanelRect); + if ImageIndex > -1 then begin + VirtualImageListMain.Draw(StatusBar.Canvas, PanelRect.Left, PanelRect.Top, ImageIndex, true); + OffsetRect(PanelRect, VirtualImageListMain.Width+2, 0); + end; + DrawText(StatusBar.Canvas.Handle, PChar(Panel.Text), -1, PanelRect, DT_SINGLELINE or DT_VCENTER); +end;} + + +{procedure TMainForm.StatusBarMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); +var + MouseP: TPoint; + Bar: TStatusBar; + PanelRect: TRect; + i: Integer; + Infos: TStringList; + HintText: String; + Conn: TDBConnection; +begin + // Display various server, client and connection related details in a hint + if IsWine then + Exit; + if (FLastHintMousepos.X = X) and (FLastHintMousepos.Y = Y) then + Exit; + FLastHintMousepos := Point(X, Y); + MouseP := StatusBar.ClientOrigin; + Inc(MouseP.X, X); + Inc(MouseP.Y, Y); + Bar := Sender as TStatusBar; + for i:=0 to Bar.Panels.Count-1 do begin + SendMessage(Bar.Handle, SB_GETRECT, i, Integer(@PanelRect)); + if PtInRect(PanelRect, FLastHintMousepos) then + break; + end; + if i = FLastHintControlIndex then + Exit; + FLastHintControlIndex := i; + if FLastHintControlIndex = 3 then begin + Conn := ActiveConnection; + if (Conn <> nil) and (not Conn.IsLockedByThread) then begin + Infos := Conn.ConnectionInfo; + HintText := ''; + for i:=0 to Infos.Count-1 do begin + HintText := HintText + Infos.Names[i] + ': ' + StrEllipsis(Infos.ValueFromIndex[i], 200) + CRLF; + end; + BalloonHint1.Description := Trim(HintText); + OffsetRect(PanelRect, Bar.ClientOrigin.X, Bar.ClientOrigin.Y); + BalloonHint1.ShowHint(PanelRect); + end; + end else + Bar.OnMouseLeave(Sender); +end;} + + +{procedure TMainForm.StatusBarMouseLeave(Sender: TObject); +begin + BalloonHint1.HideHint; + FLastHintControlIndex := -1; +end;} + + +procedure TMainForm.actExitApplicationExecute(Sender: TObject); +begin + Close; +end; + +{procedure TMainForm.actFlushExecute(Sender: TObject); +var + FlushWhat: String; +begin + if Sender = actFlushHosts then + FlushWhat := 'HOSTS' + else if Sender = actFlushLogs then + FlushWhat := 'LOGS' + else if Sender = actFlushPrivileges then + FlushWhat := 'PRIVILEGES' + else if Sender = actFlushTables then + FlushWhat := 'TABLES' + else if Sender = actFlushTableswithreadlock then + FlushWhat := 'TABLES WITH READ LOCK' + else if Sender = actFlushStatus then + FlushWhat := 'STATUS' + else + raise Exception.CreateFmt(_('Unhandled sender control: %s'), [(Sender as TControl).Name]); + try + ActiveConnection.Query('FLUSH ' + FlushWhat); + if Sender = actFlushTableswithreadlock then begin + MessageDialog( + _('Tables have been flushed and read lock acquired. Perform backup or snapshot of table data files now. Press OK to unlock when done...'), + mtInformation, [mbOk] + ); + ActiveConnection.Query('UNLOCK TABLES'); + end; + except + on E:EDbError do + ErrorDialog(E.Message); + end; +end;} + + +{procedure TMainForm.actFullRefreshExecute(Sender: TObject); +var + OldFullTableStatusSetting: Boolean; + Conn: TDBConnection; +begin + // Temorarily enable full table status when it's disabled + Conn := ActiveConnection; + OldFullTableStatusSetting := Conn.Parameters.FullTableStatus; + Conn.Parameters.FullTableStatus := True; + actRefresh.Execute; + Conn.Parameters.FullTableStatus := OldFullTableStatusSetting; +end;} + + +procedure TMainForm.actGotoDbTreeExecute(Sender: TObject); +begin + DBtree.SetFocus; +end; + + +{procedure TMainForm.actGotoFilterExecute(Sender: TObject); +begin + editTableFilter.SetFocus; +end;} + + +{procedure TMainForm.actGoToQueryResultsExecute(Sender: TObject); +var + Tab: TQueryTab; + Grid: TVirtualStringTree; +begin + if QueryTabs.HasActiveTab then begin + // Switch between query editor and result grid + Tab := QueryTabs.ActiveTab; + if Tab.Memo.Focused then begin + if Tab.ActiveResultTab <> nil then begin + Grid := Tab.ActiveResultTab.Grid; + Grid.SetFocus; + if Grid.FocusedNode = nil then + SelectNode(Grid, 0); + end else begin + MessageBeep(MB_ICONASTERISK); + end; + end else begin + Tab.Memo.SetFocus; + end; + end else if PageControlMain.ActivePage = tabData then begin + // Switch between data tab filter and result grid + if SynMemoFilter.Focused then begin + DataGrid.SetFocus; + if DataGrid.FocusedNode = nil then + SelectNode(DataGrid, 0); + end else begin + ToggleFilterPanel(True); + SynMemoFilter.TrySetFocus; + end; + end else begin + MessageBeep(MB_ICONASTERISK); + end; +end;} + + +{procedure TMainForm.actGoToDataMultiFilterExecute(Sender: TObject); +begin + // Go to multi column filter generator + if PageControlMain.ActivePage = tabData then begin + ToggleFilterPanel(True); + editFilterSearch.TrySetFocus; + end else begin + MessageBeep(MB_ICONASTERISK); + end; +end;} + + +{procedure TMainForm.actGotoTabNumberExecute(Sender: TObject); +var + i, Visibles, WantedIndex: Integer; +begin + // Set focus on tab by numeric index + WantedIndex := -1; + if Sender = actGotoTab1 then + WantedIndex := 0 + else if Sender = actGotoTab2 then + WantedIndex := 1 + else if Sender = actGotoTab3 then + WantedIndex := 2 + else if Sender = actGotoTab4 then + WantedIndex := 3 + else if Sender = actGotoTab5 then + WantedIndex := 4; + i := 0; + Visibles := 0; + while true do begin + if i >= PageControlMain.PageCount then + Break; + if PageControlMain.Pages[i].TabVisible then begin + if Visibles = WantedIndex then begin + PageControlMain.ActivePageIndex := i; + Break; + end; + Inc(Visibles); + end; + Inc(i); + end; +end;} + + +{procedure TMainForm.actGridEditFunctionExecute(Sender: TObject); +begin + // Insert SQL function in grid + FGridEditFunctionMode := True; + ActiveGrid.EditNode(ActiveGrid.FocusedNode, ActiveGrid.FocusedColumn); +end;} + + +{procedure TMainForm.StoreLastSessions; +var + OpenSessions, SessionPaths, SortedSessions: TStringList; + Connection: TDBConnection; + JumpTask: TJumpTask; + SessionPath: String; + i: Integer; + LastConnect: TDateTime; +begin + // Store names of open sessions + OpenSessions := TStringList.Create; + for Connection in Connections do + OpenSessions.Add(Connection.Parameters.SessionPath); + AppSettings.WriteString(asLastSessions, Implode(DELIM, OpenSessions)); + OpenSessions.Free; + if Assigned(ActiveConnection) then + AppSettings.WriteString(asLastActiveSession, ActiveConnection.Parameters.SessionPath); + + // Recreate Win7 taskbar jump list with sessions used in the last month, ordered by the number of connects + if Assigned(FJumpList) then try + FJumpList.Clear; + SessionPaths := TStringList.Create; + SortedSessions := TStringList.Create; + AppSettings.GetSessionPaths('', SessionPaths); + for SessionPath in SessionPaths do begin + AppSettings.SessionPath := SessionPath; + LastConnect := StrToDateTimeDef(AppSettings.ReadString(asLastConnect), DateTimeNever); + if DaysBetween(LastConnect, Now) <= 30 then + SortedSessions.Values[SessionPath] := IntToStr(AppSettings.ReadInt(asConnectCount)); + end; + SessionPaths.Free; + AppSettings.ResetPath; + SortedSessions.CustomSort(StringListCompareByValue); + for i:=0 to SortedSessions.Count-1 do begin + JumpTask := TJumpTask.Create; + JumpTask.Title := SortedSessions.Names[i]+' ('+FormatNumber(SortedSessions.ValueFromIndex[i], True)+')'; + JumpTask.ApplicationPath := ParamStr(0); + JumpTask.Arguments := '-d="'+SortedSessions.Names[i]+'"'; + JumpTask.CustomCategory := _('Recent sessions'); + FJumpList.JumpItems.Add(JumpTask); + end; + SortedSessions.Free; + // Seems to randomly produce access violations, not only on Wine + // See issue #3428 + FJumpList.Apply; + except + on E:Exception do + LogSQL(E.Message, lcError); + end; +end;} + + +{procedure TMainForm.FormAfterMonitorDpiChanged(Sender: TObject; OldDPI, + NewDPI: Integer); +begin + // DPI settings change finished + FormResize(Sender); +end;} + +{procedure TMainForm.FormBeforeMonitorDpiChanged(Sender: TObject; OldDPI, + NewDPI: Integer); +var + Factor: Extended; +begin + // Moving window to different screen or user changed DPI setting for current screen + Factor := 100 / PixelsPerInchDesigned * NewDPI; + LogSQL(f_('Scaling controls to screen DPI: %d%%', [Round(Factor)])); + //LogSQL('PixelsPerInchDesigned:'+PixelsPerInchDesigned.ToString+' OldDPI:'+OldDPI.ToString+' NewDPI:'+NewDPI.ToString); +end;} + + +{procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); +var + i: Integer; +begin + // Prompt on modified changes + CanClose := True; + + // Unsaved changes in some query tab? + // Also backups automatically if option is activated + for i:=0 to QueryTabs.Count-1 do begin + CanClose := ConfirmTabClose(i+tabQuery.PageIndex, True); + if not CanClose then + Exit; + end; + // Unsaved modified table, trigger, view or routine? + if Assigned(ActiveObjectEditor) then + CanClose := not (ActiveObjectEditor.DeInit in [mrAbort, mrCancel]); +end;} + +{procedure TMainForm.FormDestroy(Sender: TObject); +begin + // Destroy dialogs + FreeAndNil(FSearchReplaceDialog); + + StoreLastSessions; + + // Store tab setup for the last time, before tabs are destroyed + if TimerStoreTabs.Enabled then begin + TimerStoreTabs.OnTimer(Sender); + end; + + // Some grid editors access the registry - be sure these are gone before freeing AppSettings + QueryTabs.Clear; + DataGrid.EndEditNode; + + // Clearing query and browse data. + FreeAndNil(DataGridResult); + + // Close database connections + Connections.Clear; + + // Save various settings + AppSettings.WriteBool(asStopOnErrorsInBatchMode, actQueryStopOnErrors.Checked); + AppSettings.WriteBool(asDisplayBLOBsAsText, actBlobAsText.Checked); + AppSettings.WriteString(asDelimiter, FDelimiter); + AppSettings.WriteInt(asQuerymemoheight, pnlQueryMemo.Height); + AppSettings.WriteInt(asQueryhelperswidth, pnlQueryHelpers.Width); + AppSettings.WriteInt(asCompletionProposalWidth, SynCompletionProposal.Width); + AppSettings.WriteInt(asCompletionProposalNbLinesInWindow, SynCompletionProposal.NbLinesInWindow); + AppSettings.WriteInt(asDbtreewidth, pnlLeft.width); + AppSettings.WriteBool(asGroupTreeObjects, actGroupObjects.Checked); + AppSettings.WriteInt(asDataPreviewHeight, pnlPreview.Height); + AppSettings.WriteBool(asDataPreviewEnabled, actDataPreview.Checked); + AppSettings.WriteInt(asLogHeight, SynMemoSQLLog.Height); + AppSettings.WriteBool(asFilterPanel, actFilterPanel.Checked); + AppSettings.WriteBool(asWrapLongLines, actQueryWordWrap.Checked); + AppSettings.WriteBool(asCodeFolding, actCodeFolding.Checked); + AppSettings.WriteBool(asSingleQueries, actSingleQueries.Checked); + AppSettings.WriteBool(asLogHorizontalScrollbar, actLogHorizontalScrollbar.Checked); + AppSettings.WriteBool(asMainWinMaximized, WindowState=wsMaximized); + AppSettings.WriteInt(asMainWinOnMonitor, Monitor.MonitorNum); + // Window dimensions are only valid when WindowState is normal. + if WindowState = wsNormal then begin + AppSettings.WriteInt(asMainWinLeft, Left); + AppSettings.WriteInt(asMainWinTop, Top); + AppSettings.WriteIntDpiAware(asMainWinWidth, Self, Width); + AppSettings.WriteIntDpiAware(asMainWinHeight, Self, Height); + end; + SaveListSetup(ListDatabases); + SaveListSetup(ListVariables); + SaveListSetup(ListStatus); + SaveListSetup(ListProcesses); + SaveListSetup(ListCommandStats); + SaveListSetup(ListTables); + + LogToFile := False; + AppSettings.Free; +end;} +procedure TMainForm.FormCreate(Sender: TObject); +var + i, j, MonitorIndex: Integer; + //QueryTab: TQueryTab; + //Action, CopyAsAction: TAction; + //ExportFormat: TGridExportFormat; + VI: TVersionInfo; + CopyAsMenu, CommandMenu: TMenuItem; + //TZI: TTimeZoneInformation; + //dti: TDBDatatypeCategoryIndex; + //EditorCommand: TSynEditorCommand; + CmdCap: String; + Lib: TMySQLLib; + test: String; +begin + {*** + OnCreate Event + Important to set the windowstate here instead of in OnShow + because possible windowstate-switching is done with an animation + if set in Windows. This animation takes some milliseconds + to complete and can be annoying. + } + Caption := APPNAME; + + // Load preferred ImageCollection into VirtualImageList + {PrepareImageList; + + if AppSettings.ReadBool(asToolbarShowCaptions) then begin + for i:=0 to ToolBarMainButtons.ButtonCount-1 do begin + if ToolBarMainButtons.Buttons[i].Style = tbsSeparator then + Continue; + ToolBarMainButtons.Buttons[i].AutoSize := True; + //ToolBarMainButtons.Buttons[i].AutoSize := False; + //ToolBarMainButtons.Buttons[i].Width := 25; + ToolBarMainButtons.Buttons[i].Style := tbsTextButton; + end; + //ToolBarMainButtons.AllowTextButtons := True; + //ToolBarMainButtons.List := True; + ToolBarMainButtons.ShowCaptions := true; + end;} + + {// Translate menu items + menuQueryHelpersGenerateSelect.Caption := f_('Generate %s ...', ['SELECT']); + menuQueryHelpersGenerateInsert.Caption := f_('Generate %s ...', ['INSERT']); + menuQueryHelpersGenerateUpdate.Caption := f_('Generate %s ...', ['UPDATE']); + menuQueryHelpersGenerateDelete.Caption := f_('Generate %s ...', ['DELETE']);} + + {// Translate data type categories + for dti:=Low(DatatypeCategories) to High(DatatypeCategories) do begin + DatatypeCategories[dti].Name := _(DatatypeCategories[dti].Name); + end;} + + // Detect version + try + VI := TVersionInfo.Create; + VI.Load(HINSTANCE); + FAppVerMajor := VI.FixedInfo.FileVersion[0]; + FAppVerMinor := VI.FixedInfo.FileVersion[1]; + FAppVerRelease := VI.FixedInfo.FileVersion[2]; + FAppVerRevision := VI.FixedInfo.FileVersion[3]; + VI.Free; + except + FAppVerMajor := 0; + FAppVerMinor := 0; + FAppVerRelease := 0; + FAppVerRevision := 0; + end; + FAppVersion := Format('%d.%d.%d.%d', [FAppVerMajor, FAppVerMinor, FAppVerRelease, FAppVerRevision]); + + {// Taskbar button interface for Windows 7 + // Possibly fails. See http://www.heidisql.com/forum.php?t=22451 + if CheckWin32Version(6, 1) then + try + TaskbarList := CreateComObject(CLSID_TaskbarList) as ITaskbarList; + TaskbarList.HrInit; + Supports(TaskbarList, IID_ITaskbarList2, TaskbarList2); + Supports(TaskbarList, IID_ITaskbarList3, TaskbarList3); + Supports(TaskbarList, IID_ITaskbarList4, TaskbarList4); + except + on E:EOleSysError do; + end;} + + // Load snippet filenames + {SetSnippetFilenames;} + + // Dynamically create actions and menuitems in "Copy as" context menu + {for ExportFormat:=Low(TGridExportFormat) to High(TGridExportFormat) do begin + CopyAsAction := TAction.Create(ActionList1); + CopyAsAction.ActionList := ActionList1; + CopyAsAction.Category := actExportData.Category; + CopyAsAction.Name := TfrmExportGrid.CopyAsActionPrefix + Integer(ExportFormat).ToString; + CopyAsAction.Caption := TfrmExportGrid.FormatToDescription[ExportFormat]; + CopyAsAction.ImageIndex := TfrmExportGrid.FormatToImageIndex[ExportFormat]; + CopyAsAction.Tag := Integer(ExportFormat); + CopyAsAction.OnExecute := actCopyOrCutExecute; + CopyAsMenu := TMenuItem.Create(popupDataGrid); + CopyAsMenu.Action := CopyAsAction; + menuCopyAs.Add(CopyAsMenu); + end;} + + // Generate submenu with SynEdit commands + {FEditorCommandStrings := TStringList.Create; + SynEditKeyCmds.GetEditorCommandValues(AddEditorCommandMenu); + for i:=0 to FEditorCommandStrings.Count-1 do begin + EditorCommand := ConvertCodeStringToCommand(FEditorCommandStrings[i]); + CommandMenu := TMenuItem.Create(MainMenu1); + CmdCap := FEditorCommandStrings[i]; + CmdCap := Copy(CmdCap, 3, Length(CmdCap)-2); + // Insert spaces before uppercase chars + for j:=Length(CmdCap) downto 1 do begin + if (j > 1) and CmdCap[j].IsUpper then + Insert(' ', CmdCap, j); + end; + CommandMenu.Caption := CmdCap; + for j:=0 to SynMemoQuery.Keystrokes.Count-1 do begin + if SynMemoQuery.Keystrokes[j].Command = EditorCommand then begin + CommandMenu.Caption := CommandMenu.Caption + ' (' + ShortCutToText(SynMemoQuery.Keystrokes[j].ShortCut) + ')'; + Break; + end; + end; + CommandMenu.OnClick := EditorCommandOnClick; + menuEditorCommands.Add(CommandMenu); + end;} + + + {Delimiter := AppSettings.ReadString(asDelimiter);} + + // Define static query tab as first one in our QueryTabs list + {QueryTab := TQueryTab.Create(Self); + QueryTab.TabSheet := tabQuery; + QueryTab.Number := 1; + QueryTab.Uid := TQueryTab.GenerateUid; + QueryTab.pnlMemo := pnlQueryMemo; + QueryTab.pnlHelpers := pnlQueryHelpers; + QueryTab.filterHelpers := filterQueryHelpers; + QueryTab.treeHelpers := treeQueryHelpers; + QueryTab.Memo := SynMemoQuery; + QueryTab.MemoLineBreaks := TLineBreaks(AppSettings.ReadInt(asLineBreakStyle)); + QueryTab.spltHelpers := spltQueryHelpers; + QueryTab.spltQuery := spltQuery; + QueryTab.tabsetQuery := tabsetQuery; + InheritFont(QueryTab.tabsetQuery.Font); + QueryTab.ResultTabs := TResultTabs.Create(True); + + QueryTabs := TQueryTabList.Create(True); + QueryTabs.Add(QueryTab);} + + // Populate generic results for "Host" subtabs + {FHostListResults := TDBQueryList.Create(False); + for i:=0 to PageControlHost.PageCount-1 do begin + FHostListResults.Add(nil); + end;} + + // Enable auto completion in data tab, filter editor + {SynCompletionProposal.AddEditor(SynMemoFilter);} + + // Window position + {Left := AppSettings.ReadInt(asMainWinLeft); + Top := AppSettings.ReadInt(asMainWinTop); + // ... state + if AppSettings.ReadBool(asMainWinMaximized) then + WindowState := wsMaximized; + // ... and monitor placement + MonitorIndex := AppSettings.ReadInt(asMainWinOnMonitor); + MonitorIndex := Max(0, MonitorIndex); + MonitorIndex := Min(Screen.MonitorCount-1, MonitorIndex); + MakeFullyVisible(Screen.Monitors[MonitorIndex]);} + + {actQueryStopOnErrors.Checked := AppSettings.ReadBool(asStopOnErrorsInBatchMode); + actBlobAsText.Checked := AppSettings.ReadBool(asDisplayBLOBsAsText); + actQueryWordWrap.Checked := AppSettings.ReadBool(asWrapLongLines); + if AppSettings.ReadBool(asCodeFolding) and (not actCodeFolding.Checked) then + actCodeFolding.Execute; + actSingleQueries.Checked := AppSettings.ReadBool(asSingleQueries); + actBatchInOneGo.Checked := not AppSettings.ReadBool(asSingleQueries); + actPreferencesLogging.ImageIndex := actPreferences.ImageIndex; + actPreferencesLogging.OnExecute := actPreferences.OnExecute; + actPreferencesData.ImageIndex := actPreferences.ImageIndex; + actPreferencesData.OnExecute := actPreferences.OnExecute; + menuAlwaysGenerateFilter.Checked := AppSettings.ReadBool(asAlwaysGenerateFilter); } + + {pnlQueryMemo.Height := AppSettings.ReadInt(asQuerymemoheight); + pnlQueryHelpers.Width := AppSettings.ReadInt(asQueryhelperswidth); + pnlLeft.Width := AppSettings.ReadInt(asDbtreewidth); + pnlPreview.Height := AppSettings.ReadInt(asDataPreviewHeight); + if AppSettings.ReadBool(asDataPreviewEnabled) then + actDataPreviewExecute(actDataPreview); + SynMemoSQLLog.Height := Max(AppSettings.ReadInt(asLogHeight), spltTopBottom.MinSize); + // Force status bar position to below log memo + StatusBar.Top := SynMemoSQLLog.Top + SynMemoSQLLog.Height; + actDataShowNext.Hint := f_('Show next %s rows ...', [FormatNumber(AppSettings.ReadInt(asDatagridRowsPerStep))]); + actAboutBox.Caption := f_('About %s', [APPNAME+' '+FAppVersion]); + // Activate logging + LogToFile := AppSettings.ReadBool(asLogToFile); + if AppSettings.ReadBool(asLogHorizontalScrollbar) then + actLogHorizontalScrollbar.Execute;} + + {// Data-Font: + ApplyFontToGrids; + // Load color settings + DatatypeCategories[dtcInteger].Color := AppSettings.ReadInt(asFieldColorNumeric); + DatatypeCategories[dtcReal].Color := AppSettings.ReadInt(asFieldColorReal); + DatatypeCategories[dtcText].Color := AppSettings.ReadInt(asFieldColorText); + DatatypeCategories[dtcBinary].Color := AppSettings.ReadInt(asFieldColorBinary); + DatatypeCategories[dtcTemporal].Color := AppSettings.ReadInt(asFieldColorDatetime); + DatatypeCategories[dtcSpatial].Color := AppSettings.ReadInt(asFieldColorSpatial); + DatatypeCategories[dtcOther].Color := AppSettings.ReadInt(asFieldColorOther); + CalcNullColors;} + + {FDataGridSortItems := TSortItems.Create(True);} + + {DataLocalNumberFormat := AppSettings.ReadBool(asDataLocalNumberFormat); + DataGridTable := nil; + FActiveDbObj := nil;} + + {// Database tree options + actGroupObjects.Checked := AppSettings.ReadBool(asGroupTreeObjects); + if AppSettings.ReadBool(asDisplayObjectSizeColumn) then + menuShowSizeColumn.Click; + if AppSettings.ReadBool(asAutoExpand) then + menuAutoExpand.Click; + if AppSettings.ReadBool(asDoubleClickInsertsNodeText) then + menuDoubleClickInsertsNodeText.Click;} + + {// Shortcuts + FActionList1DefaultCaptions := TStringList.Create; + FActionList1DefaultHints := TStringList.Create; + for i:=0 to ActionList1.ActionCount-1 do begin + Action := TAction(ActionList1.Actions[i]); + Action.ShortCut := AppSettings.ReadInt(asActionShortcut1, Action.Name, Action.ShortCut); + FActionList1DefaultCaptions.Insert(i, Action.Caption); + FActionList1DefaultHints.Insert(i, Action.Hint); + end;} + + {// Completion proposal window + // The proposal form gets scaled a second time when it shows its form with Scaled=True. + // We already store and restore the dimensions DPI aware. + SynCompletionProposal.Form.Scaled := False; + SynCompletionProposal.TimerInterval := AppSettings.ReadInt(asCompletionProposalInterval); + SynCompletionProposal.Width := AppSettings.ReadInt(asCompletionProposalWidth); + SynCompletionProposal.NbLinesInWindow := AppSettings.ReadInt(asCompletionProposalNbLinesInWindow);} + + {// Place progressbar on the statusbar + ProgressBarStatus.Parent := StatusBar; + ProgressBarStatus.Visible := False;} + + // SynMemo font, hightlighting and shortcuts + {SetupSynEditors;} + + {PageControlMain.MultiLine := AppSettings.ReadBool(asTabsInMultipleLines); + SetMainTab(tabHost); + FBtnAddTab := TSpeedButton.Create(PageControlMain); + FBtnAddTab.Parent := PageControlMain; + VirtualImageListMain.GetBitmap(actNewQueryTab.ImageIndex, FBtnAddTab.Glyph); + FBtnAddTab.Height := PageControlMain.TabRect(0).Bottom - PageControlMain.TabRect(0).Top - 2; + FBtnAddTab.Width := FBtnAddTab.Height; + FBtnAddTab.Flat := True; + FBtnAddTab.Hint := actNewQueryTab.Hint; + FBtnAddTab.OnClick := actNewQueryTab.OnExecute;} + + {// Filter panel + VirtualImageListMain.GetBitmap(134, btnCloseFilterPanel.Glyph); + if AppSettings.ReadBool(asFilterPanel) then + actFilterPanelExecute(nil); + lblFilterVTInfo.Caption := '';} + + {SelectedTableColumns := TTableColumnList.Create; + SelectedTableKeys := TTableKeyList.Create; + SelectedTableForeignKeys := TForeignKeyList.Create; + SelectedTableTimestampColumns := TStringList.Create;} + + {// Set up connections list + FConnections := TDBConnectionList.Create; + FConnections.OnNotify := ConnectionsNotify;} + + {FTreeRefreshInProgress := False; + FGridCopying := False; + FGridPasting := False;} + + {FileEncodings := Explode(',', _('Auto detect (may fail)')+',ANSI,ASCII,Unicode,Unicode Big Endian,UTF-8,UTF-7,UTF-8-BOM');} + + {// Detect timezone offset in seconds, once + case GetTimeZoneInformation(TZI) of + TIME_ZONE_ID_STANDARD: FTimeZoneOffset := (TZI.Bias + TZI.StandardBias); + TIME_ZONE_ID_DAYLIGHT: FTimeZoneOffset := (TZI.Bias + TZI.DaylightBias); + TIME_ZONE_ID_UNKNOWN: FTimeZoneOffset := TZI.Bias; + else RaiseLastOSError; + end; + FTimeZoneOffset := FTimeZoneOffset * 60;} + + // Set noderoot for query helpers box + {treeQueryHelpers.RootNodeCount := 7;} + + // Initialize taskbar jump list + {if not IsWine then begin + FJumpList := TJumpList.Create; + FJumpList.ApplicationId := APPNAME + IntToStr(GetExecutableBits); + end;} + + FLastCaptionChange := 0; + FLastPortableSettingsSave := 0; + FLastAppSettingsWrites := 0; + //FFormatSettings := TFormatSettings.Create('en-US'); + FDefaultHintFontName := Screen.HintFont.Name; + + // Now we are free to use certain methods, which are otherwise fired too early + MainFormCreated := True; + + // Log some application details - useful when analyzing session logs + LogSQL(f_('App path: "%s"', [Application.ExeName]), lcDebug); + LogSQL(f_('Version: "%s"', [AppVersion]), lcDebug); + //LogSQL(f_('Theme: "%s"', [TStyleManager.ActiveStyle.Name]), lcDebug); + LogSQL(f_('Pixels per inch on current monitor: %d', [Monitor.PixelsPerInch]), lcDebug); + LogSQL(f_('Timezone offset: %d', [FTimeZoneOffset]), lcDebug); + + {Lib := TMySQLLib.Create(ExtractFilePath(Application.ExeName)+'libmariadb.dll', 'libmariadb.dll'); + try + test := Lib.mysql_get_client_info; + LogSQL(test); + //ShowMessage(String(Lib.mysql_get_client_info)); + except on E:Exception do + ShowMessage('Wrong error: '+E.Message); + end;} +end; + + +procedure TMainForm.AfterFormCreate; +{var + LastSessions, FileNames: TStringlist; + Connection: TDBConnection; + LoadedParams, ConnectionParams: TConnectionParameters; + LastUpdatecheck, LastStatsCall, LastConnect: TDateTime; + UpdatecheckInterval, i: Integer; + LastActiveSession, Environment, RunFrom: String; + frm : TfrmUpdateCheck; + StatsCall: THttpDownload; + SessionPaths: TStringlist; + DlgResult: TModalResult; + Tab: TQueryTab; + SessionManager: TConnForm;} +begin + {// Check for connection parameters on commandline or show connections form. + if AppSettings.ReadBool(asUpdatecheck) then begin + // Do an updatecheck if checked in settings + LastUpdatecheck := StrToDateTimeDef(AppSettings.ReadString(asUpdatecheckLastrun), DateTimeNever); + UpdatecheckInterval := AppSettings.ReadInt(asUpdatecheckInterval); + if DaysBetween(Now, LastUpdatecheck) >= UpdatecheckInterval then begin + frm := TfrmUpdateCheck.Create(Self); + frm.btnCancel.Caption := _('Skip'); + try + frm.ReadCheckFile; + // Show the dialog if release is available, or - when wanted - build checks are activated + if (AppSettings.ReadBool(asUpdatecheckBuilds) and frm.btnBuild.Enabled) + or frm.LinkLabelRelease.Enabled then begin + frm.ShowModal; + end; + except + on E:Exception do + LogSQL(f_('Error when checking for updates: %s', [E.Message])); + end; + frm.Free; // FormClose has no caFree, as it may not have been called + end; + end;} + + {// Get all session names + SessionPaths := TStringList.Create; + AppSettings.GetSessionPaths('', SessionPaths);} + + {// Probably hide image + FHasDonatedDatabaseCheck := nbUnset; + ToolBarDonate.Visible := HasDonated(True) <> nbTrue;} + + {// Call user statistics if checked in settings + if AppSettings.ReadBool(asDoUsageStatistics) then begin + LastStatsCall := StrToDateTimeDef(AppSettings.ReadString(asLastUsageStatisticCall), DateTimeNever); + if DaysBetween(Now, LastStatsCall) >= 30 then begin + // Report used app version, bits, and theme name (so we find mostly unused ones for removal) + // Also report environment: WinDesktop, WinUWP or Wine + + if IsWine then Environment := 'Wine' + else if AppSettings.PortableMode then Environment := 'WinDesktopPortable' + else Environment := 'WinDesktop'; + + StatsCall := THttpDownload.Create(Self); + StatsCall.URL := APPDOMAIN + 'savestats.php?c=' + IntToStr(FAppVerRevision) + + '&bits=' + IntToStr(GetExecutableBits) + + '&thm=' + EncodeURLParam(TStyleManager.ActiveStyle.Name) + + '&env=' + EncodeURLParam(Environment) + + '&winver=' + EncodeURLParam(IntToStr(Win32MajorVersion)+'.'+IntToStr(Win32MinorVersion)); + // Enumerate actively used server versions + for i:=0 to SessionPaths.Count-1 do begin + AppSettings.SessionPath := SessionPaths[i]; + LastConnect := StrToDateTimeDef(AppSettings.ReadString(asLastConnect), DateTimeNever); + if LastConnect > LastStatsCall then begin + StatsCall.URL := StatsCall.URL + '&s[]=' + IntToStr(AppSettings.ReadInt(asNetType)) + '-' + IntToStr(AppSettings.ReadInt(asServerVersion)); + end; + end; + AppSettings.ResetPath; + try + StatsCall.SendRequest(''); + AppSettings.WriteString(asLastUsageStatisticCall, DateTimeToStr(Now)); + except + // Silently ignore it when the url could not be called over the network. + end; + FreeAndNil(StatsCall); + end; + end;} + + {ConnectionParams := nil; + RunFrom := ''; + ParseCommandLine(GetCommandLine, ConnectionParams, FileNames, RunFrom); + + // Delete scheduled task from previous + if RunFrom = 'scheduler' then begin + DeleteRestartTask; + if HasDonated(False) <> nbTrue then begin + apphelpers.ShellExec(APPDOMAIN + 'after-updatecheck?rev=' + AppVerRevision.ToString); + end; + end; + + if ConnectionParams <> nil then begin + // Minimal parameter for command line mode is hostname + try + InitConnection(ConnectionParams, True, Connection); + except on E:Exception do + ErrorDialog(E.Message); + end; + end else if AppSettings.ReadBool(asAutoReconnect) then begin + // Auto connection via preference setting + // Do not autoconnect if we're in commandline mode and the connection was not successful + LastSessions := Explode(DELIM, AppSettings.ReadString(asLastSessions)); + LastActiveSession := AppSettings.ReadString(asLastActiveSession); + for i:=LastSessions.Count-1 downto 0 do begin + if SessionPaths.IndexOf(LastSessions[i]) = -1 then + LastSessions.Delete(i); + end; + if LastSessions.Count > 0 then begin + if LastSessions.IndexOf(LastActiveSession) = -1 then + LastActiveSession := LastSessions[0]; + for i:=0 to LastSessions.Count-1 do begin + try + LoadedParams := TConnectionParameters.Create(LastSessions[i]); + InitConnection(LoadedParams, LastActiveSession=LastSessions[i], Connection); + except on E:Exception do + ErrorDialog(E.Message); + end; + end; + end; + end;} + + {// Display session manager + if Connections.Count = 0 then begin + // Cannot be done in OnCreate because we need ready forms here: + SessionManager := TConnForm.Create(Self); + DlgResult := mrCancel; + try + DlgResult := SessionManager.ShowModal; + SessionManager.Free; + except + // Work around VCL bug: Suppress access violation in TCustomForm.IsFormSizeStored + // when closing dialog via Alt+F4 + end; + if DlgResult = mrCancel then begin + Free; + Exit; + end; + end;} + + {// Restore backup'ed query tabs + if AppSettings.RestoreTabsInitValue then begin + TimerStoreTabs.Enabled := RestoreTabs; + end;} + + {// Load SQL file(s) by command line + if not RunQueryFiles(FileNames, nil, false) then begin + for i:=0 to FileNames.Count-1 do begin + Tab := GetOrCreateEmptyQueryTab(False); + Tab.LoadContents(FileNames[i], True, nil); + if i = FileNames.Count-1 then + SetMainTab(Tab.TabSheet); + end; + end;} + + MainFormAfterCreateDone := True; +end; + + +{function TMainForm.InitTabsIniFile: TIniFile; +var + WaitingSince: UInt64; + Attempts: Integer; + TabsIniFilename: String; +begin + // Try to open tabs.ini for writing or reading + // Taking multiple application instances into account + if AppSettings.PortableMode then + TabsIniFilename := ExtractFilePath(Application.ExeName) + 'tabs.ini' + else + TabsIniFilename := AppSettings.DirnameUserAppData + 'tabs.ini'; + WaitingSince := GetTickCount64; + Attempts := 0; + while not FileIsWritable(TabsIniFilename) do begin + if GetTickCount64 - WaitingSince > 3000 then + Raise Exception.Create(f_('Could not open file %s', [TabsIniFilename])); + Sleep(200); + Inc(Attempts); + end; + if Attempts > 0 then begin + LogSQL(Format('Had to wait %d ms before opening %s', [GetTickCount64 - WaitingSince, TabsIniFilename]), lcDebug); + end; + // Catch errors when file cannot be created + if not FileExists(TabsIniFilename) then begin + SaveUnicodeFile(TabsIniFilename, '', UTF8NoBOMEncoding); + end; + Result := TIniFile.Create(TabsIniFilename); +end;} + + +{procedure TMainForm.StoreTabs; +var + Tab: TQueryTab; + Section, TabCaption: String; + Sections: TStringList; + TabsIni: TIniFile; + SectionTabExists: Boolean; + pid: Cardinal; +begin + // Store query tab unsaved contents and setup, in tabs.ini + + try + TabsIni := InitTabsIniFile; + + for Tab in QueryTabs do begin + Tab.BackupUnsavedContent; + Section := Tab.Uid; + + // Avoid writing the tabs.ini file if nothing was effectively changed + TabCaption := Tab.TabSheet.Caption; + TabCaption := TabCaption.Trim([' ','*']); + if ExecRegExpr('^'+QuoteRegExprMetaChars(_('Query')+' #')+'\d+$', TabCaption) then + TabCaption := ''; + if TabsIni.ReadString(Section, TQueryTab.IdentBackupFilename, '') <> Tab.MemoBackupFilename then + TabsIni.WriteString(Section, TQueryTab.IdentBackupFilename, Tab.MemoBackupFilename); + if TabsIni.ReadString(Section, TQueryTab.IdentFilename, '') <> Tab.MemoFilename then + TabsIni.WriteString(Section, TQueryTab.IdentFilename, Tab.MemoFilename); + if TabsIni.ReadString(Section, TQueryTab.IdentCaption, '') <> TabCaption then + TabsIni.WriteString(Section, TQueryTab.IdentCaption, TabCaption); + if TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0) <> Integer(GetCurrentProcessId) then + TabsIni.WriteInteger(Section, TQueryTab.IdentPid, Integer(GetCurrentProcessId)); + if TabsIni.ReadInteger(Section, TQueryTab.IdentEditorHeight, 0) <> Tab.pnlMemo.Height then + TabsIni.WriteInteger(Section, TQueryTab.IdentEditorHeight, Tab.pnlMemo.Height); + if TabsIni.ReadInteger(Section, TQueryTab.IdentHelpersWidth, 0) <> Tab.pnlHelpers.Width then + TabsIni.WriteInteger(Section, TQueryTab.IdentHelpersWidth, Tab.pnlHelpers.Width); + if TabsIni.ReadString(Section, TQueryTab.IdentBindParams, '') <> Tab.ListBindParams.AsText then + TabsIni.WriteString(Section, TQueryTab.IdentBindParams, Tab.ListBindParams.AsText); + if TabsIni.ReadInteger(Section, TQueryTab.IdentEditorTopLine, 1) <> Tab.Memo.TopLine then + TabsIni.WriteInteger(Section, TQueryTab.IdentEditorTopLine, Tab.Memo.TopLine); + if TabsIni.ReadBool(Section, TQueryTab.IdentTabFocused, False) <> (Tab.TabSheet = Tab.TabSheet.PageControl.ActivePage) then + TabsIni.WriteBool(Section, TQueryTab.IdentTabFocused, (Tab.TabSheet = Tab.TabSheet.PageControl.ActivePage)); + if TabsIni.ReadString(Section, TQueryTab.IdentFileEncoding, 'UTF-8') <> Tab.FileEncoding then + TabsIni.WriteString(Section, TQueryTab.IdentFileEncoding, Tab.FileEncoding); + end; + + // Tabs with deleted backup files don't get restored anyway. But a section from a closed user loaded tab + // still needs to be erased. Otherwise it's loaded on next app start again. + Sections := TStringList.Create; + TabsIni.ReadSections(Sections); + for Section in Sections do begin + // Loop through local tabs + SectionTabExists := False; + for Tab in QueryTabs do begin + if Tab.Uid = Section then begin + SectionTabExists := True; + Break; + end; + end; + // Delete tab section if tab was closed and section belongs to this app instance + pid := Cardinal(TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0)); + if (not SectionTabExists) and (pid = GetCurrentProcessId) then begin + TabsIni.EraseSection(Section); + end; + end; + + // Close file + TabsIni.Free; + except + on E:Exception do begin + TimerStoreTabs.Enabled := False; + ErrorDialog(_('Storing tab setup failed'), + 'Tabs won''t be stored in this session.' + CRLF + CRLF + + E.Message + CRLF + CRLF + + SysErrorMessage(GetLastError) + ); + end; + end; +end;} + + +{function TMainForm.RestoreTabs: Boolean; +var + Tab: TQueryTab; + Sections, SlowTabs: TStringList; + Section, Filename, BackupFilename, TabCaption: String; + TabsIni: TIniFile; + pid: Cardinal; + EditorHeight, HelpersWidth, EditorTopLine: Integer; + BindParams: String; + TabFocused: Boolean; + TabLoadStart, TabLoadTime: UInt64; + Encoding: TEncoding; +const + SlowLoadMilliseconds = 5000; + + procedure CheckSlowTabLoad(CurTab: TQueryTab); + begin + TabLoadTime := GetTickCount64 - TabLoadStart; + if TabLoadTime > SlowLoadMilliseconds then begin + SlowTabs.Add('• ' + Trim(Tab.TabSheet.Caption) + ' (' + FormatTimeNumber(TabLoadTime / 1000, True) + ')'); + end; + end; +begin + // Restore query tab setup from tabs.ini + Result := True; + + try + TabsIni := InitTabsIniFile; + LogSQL('Restoring tab setup from '+TabsIni.FileName, lcDebug); + + Sections := TStringList.Create; + TabsIni.ReadSections(Sections); + SlowTabs := TStringList.Create; + + for Section in Sections do begin + TabLoadStart := GetTickCount64; + + Filename := TabsIni.ReadString(Section, TQueryTab.IdentFilename, ''); + BackupFilename := TabsIni.ReadString(Section, TQueryTab.IdentBackupFilename, ''); + TabCaption := TabsIni.ReadString(Section, TQueryTab.IdentCaption, ''); + pid := Cardinal(TabsIni.ReadInteger(Section, TQueryTab.IdentPid, 0)); + EditorHeight := TabsIni.ReadInteger(Section, TQueryTab.IdentEditorHeight, 0); + HelpersWidth := TabsIni.ReadInteger(Section, TQueryTab.IdentHelpersWidth, 0); + BindParams := TabsIni.ReadString(Section, TQueryTab.IdentBindParams, ''); + EditorTopLine := TabsIni.ReadInteger(Section, TQueryTab.IdentEditorTopLine, 1); + TabFocused := TabsIni.ReadBool(Section, TQueryTab.IdentTabFocused, False); + Encoding := GetEncodingByName(TabsIni.ReadString(Section, TQueryTab.IdentFileEncoding, 'UTF-8')); + + // Don't restore this tab if it belongs to a different running Heidi process + if (pid > 0) and (pid <> GetCurrentProcessId) and ProcessExists(pid, APPNAME) then begin + LogSQL(IfThen(BackupFilename.IsEmpty, Filename, BackupFilename)+' loaded in process #'+pid.ToString); + Continue; + end; + + // Either we have a backup file, or a user stored file. + // Both of them may not exist. + if not BackupFilename.IsEmpty then begin + if FileExists(BackupFilename) then begin + Tab := GetOrCreateEmptyQueryTab(False); + Tab.Uid := Section; + Tab.LoadContents(BackupFilename, True, Encoding); + Tab.MemoFilename := Filename; + Tab.Memo.Modified := True; + if not TabCaption.IsEmpty then + SetTabCaption(Tab.TabSheet.PageIndex, TabCaption); + if EditorHeight > 50 then + Tab.pnlMemo.Height := EditorHeight; + // Causes sporadic long-waiters: + //if HelpersWidth > 50 then + // Tab.pnlHelpers.Width := HelpersWidth; + Tab.ListBindParams.AsText := BindParams; + Tab.BindParamsActivated := Tab.ListBindParams.Count > 0; + Tab.Memo.TopLine := EditorTopLine; + if TabFocused then + SetMainTab(Tab.TabSheet); + CheckSlowTabLoad(Tab); + end else begin + // Remove tab section if backup file is gone or inaccessible for some reason + TabsIni.EraseSection(Section); + end; + end else if not Filename.IsEmpty then begin + if FileExists(Filename) then begin + Tab := GetOrCreateEmptyQueryTab(False); + Tab.Uid := Section; + Tab.LoadContents(Filename, True, Encoding); + Tab.MemoFilename := Filename; + if not TabCaption.IsEmpty then + SetTabCaption(Tab.TabSheet.PageIndex, TabCaption); + if EditorHeight > 50 then + Tab.pnlMemo.Height := EditorHeight; + // Causes sporadic long-waiters: + //if HelpersWidth > 50 then + // Tab.pnlHelpers.Width := HelpersWidth; + Tab.ListBindParams.AsText := BindParams; + Tab.BindParamsActivated := Tab.ListBindParams.Count > 0; + Tab.Memo.TopLine := EditorTopLine; + if TabFocused then + SetMainTab(Tab.TabSheet); + CheckSlowTabLoad(Tab); + end else begin + // Remove tab section if user stored file was deleted by user + TabsIni.EraseSection(Section); + end; + end; + + end; + + Sections.Free; + // Close file + TabsIni.Free; + + // Warn user about tabs which were loading slow + if SlowTabs.Count > 0 then begin + MessageDialog( + f_('%d tab(s) took longer than expected to restore. Closing and reopening these should fix that: %s', + [SlowTabs.Count, sLineBreak + sLineBreak + SlowTabs.Text]), + mtWarning, [mbOk]); + end; + SlowTabs.Free; + + except + on E:Exception do begin + Result := False; + ErrorDialog(_('Restoring tab setup failed'), + 'Tabs won''t be stored in this session.' + CRLF + CRLF + + E.Message + CRLF + CRLF + + SysErrorMessage(GetLastError) + ); + end; + end; +end;} + + +{procedure TMainForm.SetHintFontByControl(Control: TWinControl=nil); +var + UseFontName: String; +begin + // Set hint font name to match the underlying control + if Assigned(Control) and (Control is TSynMemo) then + UseFontName := TSynMemo(Control).Font.Name + else + UseFontName := FDefaultHintFontName; + if Screen.HintFont.Name <> UseFontName then + Screen.HintFont.Name := UseFontName; +end;} + + +{procedure TMainForm.TimerStoreTabsTimer(Sender: TObject); +begin + // Backup unsaved content every 10 seconds + StoreTabs; +end;} + + +procedure TMainForm.actSessionManagerExecute(Sender: TObject); +//var + //Dialog: TConnForm; +begin + //Dialog := TConnForm.Create(Self); + //Dialog.ShowModal; + //Dialog.Free; + ShowMessage('Showing session manager...'); +end; + +{procedure TMainForm.actDisconnectExecute(Sender: TObject); +var + Connection: TDBConnection; + Node: PVirtualNode; + DlgResult: Integer; + Dialog: TConnForm; +begin + // Disconnect active connection. If it's the last, exit application + Connection := ActiveConnection; + // Find and remove connection node from tree + Node := GetRootNode(DBtree, Connection); + DBTree.DeleteNode(Node); + FConnections.Remove(Connection); + // TODO: focus last session? + SelectNode(DBtree, GetNextNode(DBtree, nil)); + if FConnections.Count = 0 then begin + Dialog := TConnForm.Create(Self); + DlgResult := Dialog.ShowModal; + Dialog.Free; + if DlgResult = mrCancel then + actExitApplication.Execute; + end; +end;} + + +{procedure TMainForm.ConnectionsNotify(Sender: TObject; const Item: TDBConnection; Action: TCollectionNotification); +var + Results: TDBQuery; + Tab: TQueryTab; + ResultTab: TResultTab; + i: Integer; + Keys, NamesInKey: TStringList; + rx: TRegExpr; + ForceDeleteTableKey: Boolean; +begin + // Connection removed or added + case Action of + cnRemoved, cnExtracted: begin + // Post pending UPDATE and release current table with result + Results := GridResult(DataGrid); + if Assigned(Results) then begin + if Results.Modified then + actDataPostChangesExecute(DataGrid); + if DataGridResult = Results then begin + FreeAndNil(DataGridResult); + DataGridTable := nil; + end; + end; + + // Remove result sets which may cause AVs when disconnected + for Tab in QueryTabs do begin + if Assigned(Tab.QueryProfile) and (Tab.QueryProfile.Connection = Item) then + FreeAndNil(Tab.QueryProfile); + for ResultTab in Tab.ResultTabs do begin + if ResultTab.Results.Connection = Item then begin + Tab.ResultTabs.Clear; + Tab.tabsetQuery.Tabs.Clear; + break; + end; + end; + end; + for i:=0 to FHostListResults.Count-1 do begin + if (FHostListResults[i] <> nil) and (FHostListResults[i].Connection = Item) then begin + FHostListResults[i].Free; + FHostListResults[i] := nil; + end; + end; + + // Remove table keys if unwanted or empty + ForceDeleteTableKey := not AppSettings.ReadBool(asReuseEditorConfiguration); + AppSettings.SessionPath := Item.Parameters.SessionPath; + Keys := AppSettings.GetKeyNames; + rx := TRegExpr.Create; + rx.Expression := '.+'+QuoteRegExprMetaChars(DELIM)+'.+'; + for i:=0 to Keys.Count-1 do begin + if rx.Exec(Keys[i]) then begin + AppSettings.SessionPath := Item.Parameters.SessionPath + '\' + Keys[i]; + NamesInKey := AppSettings.GetValueNames; + if (NamesInKey.Count = 0) or ForceDeleteTableKey then begin + AppSettings.DeleteCurrentKey; + end; + end; + end; + rx.Free; + + FreeAndNil(ActiveObjectEditor); + RefreshHelperNode(TQueryTab.HelperNodeProfile); + RefreshHelperNode(TQueryTab.HelperNodeColumns); + + // Last chance to access connection related properties before disconnecting + + // Disconnect + Item.Active := False; + end; + + // New connection + cnAdded: DBTree.InsertNode(DBTree.GetLastChild(nil), amInsertAfter); + end; +end;} + + +{procedure TMainForm.actCreateDatabaseExecute(Sender: TObject); +begin + // Create database: + FCreateDatabaseDialog := TCreateDatabaseForm.Create(Self); + FCreateDatabaseDialog.ShowModal; + FreeAndNil(FCreateDatabaseDialog); +end;} + + +{procedure TMainForm.actImportCSVExecute(Sender: TObject); +var + Dialog: Tloaddataform; +begin + // Import Textfile + Dialog := Tloaddataform.Create(Self); + Dialog.ShowModal; + Dialog.Free; +end;} + +{procedure TMainForm.actPreferencesExecute(Sender: TObject); +begin + // Preferences + frmPreferences := TfrmPreferences.Create(Self); + if Sender = actPreferencesLogging then + frmPreferences.pagecontrolMain.ActivePage := frmPreferences.tabLogging + else if Sender = actPreferencesData then + frmPreferences.pagecontrolMain.ActivePage := frmPreferences.tabGridFormatting; + frmPreferences.ShowModal; + frmPreferences.Free; + frmPreferences := nil; // Important in SetupSynEditors +end;} + +{procedure TMainForm.actHelpExecute(Sender: TObject); +begin + // Display help document + Help(Sender, ''); +end;} + +{procedure TMainForm.FormResize(Sender: TObject); +var + PanelRect: TRect; + w0, w1, w2, w3, w4, w5, w6: Integer; + + function CalcPanelWidth(SampleText: String; MaxPercentage: Integer): Integer; + var + MaxPixels: Integer; + begin + MaxPixels := StatusBar.Canvas.TextWidth(SampleText) + VirtualImageListMain.Width + 20; + Result := Round(Min(MaxPixels, Width / 100 * MaxPercentage)); + end; +begin + // Exit early when user pressed "Cancel" on connection dialog + if csDestroying in ComponentState then + Exit; + // No need to resize anything if main window is minimized (= not visible) + if WindowState = wsMinimized then + Exit; + + // Super intelligent calculation of status bar panel width + w1 := CalcPanelWidth('r10 : c10 (10 KiB)', 10); + w2 := CalcPanelWidth('Connected: 1 day, 00:00 h', 10); + w3 := CalcPanelWidth('MariaDB or MySQL 5.7.6', 15); + w4 := CalcPanelWidth('Uptime: 13 days, 00:00 h', 15); + w5 := CalcPanelWidth('Server time: 20:00 ', 10); + w6 := CalcPanelWidth('DummyDummyDummyDummyDummy', 20); + w0 := StatusBar.Width - w1 - w2 - w3 - w4 - w5 - w6; + //logsql(format('IconWidth:%d 0:%d 1:%d 2:%d 3:%d 4:%d 5:%d 6:%d', [VirtualImageListMain.Width, w0, w1, w2, w3, w4, w5, w6])); + StatusBar.Panels[0].Width := w0; + StatusBar.Panels[1].Width := w1; + StatusBar.Panels[2].Width := w2; + StatusBar.Panels[3].Width := w3; + StatusBar.Panels[4].Width := w4; + StatusBar.Panels[5].Width := w5; + StatusBar.Panels[6].Width := w6; + + // Retreive the rectancle of the statuspanel (in our case the fifth panel) + if not IsWine then begin + SendMessage(StatusBar.Handle, SB_GETRECT, 5, Integer(@PanelRect)); + // Position the progressbar over the panel on the statusbar + ProgressBarStatus.SetBounds( + PanelRect.Left, + PanelRect.Top, + PanelRect.Right-PanelRect.Left, + PanelRect.Bottom-PanelRect.Top + ); + end; + + lblDataTop.Width := pnlDataTop.Width - tlbDataButtons.Width - 10; + FixQueryTabCloseButtons; + + // Right aligned button + // Do not set ToolBar.Align to alRight. See issue #1967 + if ToolBarDonate.Visible then begin + //ToolBarDonate.Width := ToolBarDonate.Buttons[0].Width; + ToolBarDonate.Left := ControlBarMain.Width - ToolBarDonate.Width; + //ToolBarDonate.Buttons[0].Height := ToolBarMainButtons.Buttons[0].Height; + end; + +end;} + +{procedure TMainForm.FormShow(Sender: TObject); +begin + // Window dimensions + if WindowState <> wsMaximized then begin + Width := AppSettings.ReadIntDpiAware(asMainWinWidth, Self); + Height := AppSettings.ReadIntDpiAware(asMainWinHeight, Self); + end; + + LogSQL(f_('Scaling controls to screen DPI: %d%%', [Round(ScaleFactor*100)])); + if TStyleManager.IsCustomStyleActive and (ScaleFactor<>1) then begin + LogSQL(f_('Caution: Style "%s" selected and non-default DPI factor - be aware that some styles appear broken with high DPI settings!', [TStyleManager.ActiveStyle.Name])); + end; + + + // Restore width of columns of all VirtualTrees + RestoreListSetup(ListDatabases); + RestoreListSetup(ListVariables); + RestoreListSetup(ListStatus); + RestoreListSetup(ListProcesses); + RestoreListSetup(ListCommandStats); + RestoreListSetup(ListTables); + + // Fix node height on Virtual Trees for current DPI settings + FixVT(DBTree); + FixVT(ListDatabases); + FixVT(ListVariables); + FixVT(ListStatus); + FixVT(ListProcesses); + FixVT(ListCommandStats); + FixVT(ListTables); + FixVT(treeQueryHelpers); + + // Manually set focus to tree - otherwise the database filter as the first + // control catches focus on startup, which is ugly. + if DBtree.CanFocus then + DBtree.SetFocus; + + // Apply resize event and call it once here in OnShow, when the form has its final dimensions + OnResize := FormResize; + OnResize(Sender); + + // Simulated link label, has non inherited blue font color + lblExplainProcess.Font.Color := clBlue; + + // Call once after all query tabs were created: + ValidateControls(Sender); +end;} + +{procedure TMainForm.AddEditorCommandMenu(const S: string); +begin + FEditorCommandStrings.Add(S); +end;} + +{procedure TMainForm.EditorCommandOnClick(Sender: TObject); +var + EditorCommand: TSynEditorCommand; + Editor: TSynMemo; +begin + EditorCommand := IndexToEditorCommand(TMenuItem(Sender).MenuIndex); + Editor := ActiveSynMemo(False); + if Assigned(Editor) then begin + Editor.BeginUndoBlock; + Editor.ExecuteCommand(EditorCommand, #0, nil); + Editor.EndUndoBlock; + end; +end;} + +{procedure TMainForm.actUserManagerExecute(Sender: TObject); +var + Dialog: TUserManagerForm; +begin + Dialog := TUserManagerForm.Create(Self); + Dialog.ShowModal; + Dialog.Free; +end;} + +{procedure TMainForm.actAboutBoxExecute(Sender: TObject); +var + Box: TAboutBox; +begin + // Info-Box + Box := TAboutBox.Create(Self); + Box.ShowModal; + Box.Free; +end;} + +{procedure TMainForm.actClearEditorExecute(Sender: TObject); +var + m: TSynMemo; +begin + if Sender = actClearQueryEditor then begin + m := QueryTabs.ActiveMemo + end else if Sender = actClearQueryLog then begin + m := SynMemoSQLLog; + m.Gutter.LineNumberStart := m.Gutter.LineNumberStart + m.Lines.Count; + end else begin + m := SynMemoFilter; + editFilterSearch.Clear; + end; + m.SelectAll; + m.SelText := ''; + m.SelStart := 0; + m.SelEnd := 0; + if Sender = actClearQueryEditor then begin + QueryTabs.ActiveTab.MemoFilename := ''; + QueryTabs.ActiveTab.Memo.Modified := False; + QueryTabs.ActiveTab.Uid := ''; + end; + if m = SynMemoFilter then begin + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); + end; +end;} + + +{procedure TMainForm.menuClearDataTabFilterClick(Sender: TObject); +begin + // Same as "Clear filter" button, but *before* the data tab is activated + AppSettings.SessionPath := GetRegKeyTable; + if AppSettings.ValueExists(asFilter) then begin + AppSettings.DeleteValue(asFilter); + LogSQL(f_('Data filter for %s deleted', [ActiveDbObj.Name]), lcInfo); + end; + if AppSettings.ValueExists(asSort) then begin + AppSettings.DeleteValue(asSort); + LogSQL(f_('Sort order for %s deleted', [ActiveDbObj.Name]), lcInfo); + end; +end;} + + +{procedure TMainForm.actTableToolsExecute(Sender: TObject); +var + Node: PVirtualNode; + DBObj: PDBObject; +begin + // Show table tools dialog + FTableToolsDialog := TfrmTableTools.Create(Self); + FTableToolsDialog.PreSelectObjects.Clear; + if DBTreeClicked(Sender) then + FTableToolsDialog.PreSelectObjects.Add(ActiveDbObj) + else begin + Node := GetNextNode(ListTables, nil, True); + while Assigned(Node) do begin + DBObj := ListTables.GetNodeData(Node); + FTableToolsDialog.PreSelectObjects.Add(DBObj^); + Node := GetNextNode(ListTables, Node, True); + end; + end; + if Sender = actMaintenance then + FTableToolsDialog.ToolMode := tmMaintenance + else if Sender = actFindTextOnServer then + FTableToolsDialog.ToolMode := tmFind + else if Sender = actExportTables then + FTableToolsDialog.ToolMode := tmSQLExport + else if Sender = actBulkTableEdit then + FTableToolsDialog.ToolMode := tmBulkTableEdit + else if Sender = actGenerateData then + FTableToolsDialog.ToolMode := tmGenerateData; + FTableToolsDialog.ShowModal; + FreeAndNil(FTableToolsDialog); +end;} + + +{procedure TMainForm.actUnixTimestampColumnExecute(Sender: TObject); +var + i: Integer; + FocusedColumnName: String; + GridColumn: TVirtualTreeColumn; +begin + // Mark focused column as UNIX timestamp column + AppSettings.SessionPath := GetRegKeyTable; + GridColumn := DataGrid.Header.Columns[DataGrid.FocusedColumn]; + FocusedColumnName := GridColumn.Text; + i := SelectedTableTimestampColumns.IndexOf(FocusedColumnName); + if i > -1 then + SelectedTableTimestampColumns.Delete(i); + if (Sender as TAction).Checked then begin + SelectedTableTimestampColumns.Add(FocusedColumnName); + if GridColumn.ImageIndex = -1 then + GridColumn.ImageIndex := 149; + end else begin + if GridColumn.ImageIndex = 149 then + GridColumn.ImageIndex := -1; + end; + if SelectedTableTimestampColumns.Count > 0 then + AppSettings.WriteString(asTimestampColumns, SelectedTableTimestampColumns.Text) + else if AppSettings.ValueExists(asTimestampColumns) then + AppSettings.DeleteValue(asTimestampColumns); + + AppSettings.ResetPath; + DataGrid.Invalidate; +end;} + + +{function TMainForm.HandleUnixTimestampColumn(Sender: TBaseVirtualTree; Column: TColumnIndex): Boolean; +var + ResultCol: Integer; +begin + // Shorthand for various places where we would normally have to add all these conditions + ResultCol := Column - 1; + Result := (Sender = DataGrid) + and (ResultCol > NoColumn) + and (DataGridResult <> nil) + and (DataGridResult.DataType(ResultCol).Category in [dtcInteger, dtcReal]) + and (SelectedTableTimestampColumns.IndexOf(DataGrid.Header.Columns[Column].Text) > -1); +end;} + + +{** + Edit view +} +{procedure TMainForm.actPrintListExecute(Sender: TObject); +var + f: TForm; +begin + // Print contents of a list or grid + f := TPrintlistForm.Create(Self); + f.ShowModal; + FreeAndNil(f); +end;} + + +{procedure TMainForm.actCopyTableExecute(Sender: TObject); +var + Dialog: TCopyTableForm; +begin + // copy table + Dialog := TCopyTableForm.Create(Self); + Dialog.ShowModal; + Dialog.Free; +end;} + + +{procedure TMainForm.actCopyTabsToSpacesExecute(Sender: TObject); +begin + // issue #1285: copy text with tabs converted to spaces + actCopyOrCutExecute(Sender); + Clipboard.TryAsText := StringReplace(Clipboard.TryAsText, #9, ' ', [rfReplaceAll]); +end;} + +{procedure TMainForm.actCopyUpdate(Sender: TObject); +begin + actCopyTabsToSpaces.Enabled := actCopy.Enabled; +end;} + +{procedure TMainForm.menuConnectionsPopup(Sender: TObject); +var + i: integer; + item: TMenuItem; + SessionPaths: TStringList; + Connection: TDBConnection; +begin + // Delete dynamically added connection menu items. + menuConnections.Items.Clear; + + // "Session manager" and "New window" items + item := TMenuItem.Create(menuConnections); + item.Action := actSessionManager; + item.Default := True; + menuConnections.Items.Add(item); + item := TMenuItem.Create(menuConnections); + item.Action := actNewWindow; + menuConnections.Items.Add(item); + item := TMenuItem.Create(menuConnections); + item.Caption := '-'; + menuConnections.Items.Add(item); + + // All sessions + SessionPaths := TStringList.Create; + AppSettings.GetSessionPaths('', SessionPaths); + for i:=0 to SessionPaths.Count-1 do begin + item := TMenuItem.Create(menuConnections); + item.Caption := EscapeHotkeyPrefix(SessionPaths[i]); + item.OnClick := SessionConnect; + for Connection in Connections do begin + if SessionPaths[i] = Connection.Parameters.SessionPath then begin + item.Checked := True; + break; + end; + end; + menuConnections.Items.Add(item); + end; + +end;} + + +{procedure TMainForm.MainMenuFileClick(Sender: TObject); +var + Item: TMenuItem; + i: Integer; + SessionPaths: TStringList; + Connection: TDBConnection; +begin + // Add all sessions to submenu + menuConnectTo.Clear; + SessionPaths := TStringList.Create; + AppSettings.GetSessionPaths('', SessionPaths); + for i:=0 to SessionPaths.Count-1 do begin + Item := TMenuItem.Create(menuConnectTo); + Item.Caption := EscapeHotkeyPrefix(SessionPaths[i]); + Item.OnClick := SessionConnect; + for Connection in Connections do begin + if SessionPaths[i] = Connection.Parameters.SessionPath then begin + Item.Checked := True; + break; + end; + end; + menuConnectTo.Add(Item); + end; +end;} + + +{procedure TMainForm.actWebbrowse(Sender: TObject); +begin + // Browse to URL (hint) + ShellExec( TAction(Sender).Hint ); +end;} + + +{procedure TMainForm.DonateClick(Sender: TObject); +var + Dialog: TWinControl; + place: String; +begin + // Click on one of the various donate buttons + if Sender is TWinControl then begin + Dialog := GetParentFormOrFrame(TWinControl(Sender)); + end else begin + Dialog := Self; + end; + if Dialog = nil then + ErrorDialog(f_('Could not determine parent form of this %s', [Sender.ClassName])) + else begin + place := LowerCase(Dialog.UnitName); + ShellExec(APPDOMAIN + 'donatebutton.php?place=' + EncodeURLParam(place)); + end; +end;} + + +{procedure TMainForm.actExportSettingsExecute(Sender: TObject); +var + Dialog: TSaveDialog; +begin + // Export settings to .txt file + Dialog := TSaveDialog.Create(Self); + Dialog.Title := f_('Export %s settings to file ...', [APPNAME]); + Dialog.DefaultExt := 'txt'; + Dialog.Filter := _('Text files')+' (*.txt)|*.txt|'+_('All files')+' (*.*)|*.*'; + Dialog.Options := Dialog.Options + [ofOverwritePrompt]; + if Dialog.Execute then try + AppSettings.ExportSettings(Dialog.FileName); + MessageDialog(f_('Settings successfully exported to %s', [Dialog.FileName]), mtInformation, [mbOK]); + except + on E:Exception do + ErrorDialog(E.Message); + end; + Dialog.Free; +end;} + + +{procedure TMainForm.actImportSettingsExecute(Sender: TObject); +var + Dialog: TOpenDialog; +begin + // Import settings from .txt or .reg file + Dialog := TOpenDialog.Create(Self); + Dialog.Title := f_('Import %s settings from file ...', [APPNAME]); + Dialog.Filter := _('Text files')+' (*.txt)|*.txt|'+_('Registry dump, deprecated')+' (*.reg)|*.reg|'+_('All files')+' (*.*)|*.*'; + ImportSettingsDone := False; + if Dialog.Execute then try + if LowerCase(ExtractFileExt(Dialog.FileName)) = 'reg' then + ShellExec('regedit.exe', '', '"'+Dialog.FileName+'"') + else begin + AppSettings.ImportSettings(Dialog.FileName); + MessageDialog(f_('Settings successfully restored from %s', [Dialog.FileName]), mtInformation, [mbOK]); + end; + ImportSettingsDone := True; + except + on E:Exception do + ErrorDialog(E.Message); + end; + Dialog.Free; +end;} + + +{function TMainForm.GetCurrentQuery(Tab: TQueryTab): String; +var + BatchAll: TSQLBatch; + Query, PrevQuery: TSQLSentence; +begin + // Return SQL query on cursor position + Result := ''; + BatchAll := TSQLBatch.Create; + BatchAll.SQL := Tab.Memo.Text; + PrevQuery := nil; + for Query in BatchAll do begin + if (Tab.Memo.SelStart >= Query.LeftOffset-1) and (Tab.Memo.SelStart < Query.RightOffset) then begin + Result := Query.SQL; + Tab.LeftOffsetInMemo := Query.LeftOffset; + break; + end; + PrevQuery := Query; + end; + // Prefer query left to the current one, if current one contains no text + if Trim(Result).IsEmpty and Assigned(PrevQuery) then begin + Result := PrevQuery.SQL; + Tab.LeftOffsetInMemo := PrevQuery.LeftOffset; + end; + BatchAll.Free; +end;} + + +{procedure TMainForm.actExecuteQueryExecute(Sender: TObject); +var + ProfileNode: PVirtualNode; + Batch: TSQLBatch; + Tab: TQueryTab; + BindParam: Integer; + NewSQL, msg, Command, SQLNoComments, CurrentQuery: String; + Query: TSQLSentence; + rx: TRegExpr; + ContainsUnsafeQueries, DoExecute: Boolean; +begin + Tab := QueryTabs.ActiveTab; + OperationRunning(True); + DoExecute := True; + + ShowStatusMsg(_('Splitting SQL queries ...')); + Batch := TSQLBatch.Create; + if Sender = actExecuteSelection then begin + Batch.SQL := Tab.Memo.SelText; + Tab.LeftOffsetInMemo := Tab.Memo.SelStart; + end else if Sender = actExecuteCurrentQuery then begin + Batch.SQL := GetCurrentQuery(Tab); + end else if Sender = actExplainCurrentQuery then begin + CurrentQuery := GetCurrentQuery(Tab); + if CurrentQuery.Trim.IsEmpty then begin + ErrorDialog(_('Current query is empty'), _('Please move the cursor inside the query you want to use.')); + DoExecute := False; + end else begin + Batch.SQL := 'EXPLAIN ' + CurrentQuery; + end; + end else begin + Batch.SQL := Tab.Memo.Text; + Tab.LeftOffsetInMemo := 0; + end; + + // Check if there is bind parameters + if (tab.ListBindParams.Count > 0) and DoExecute then begin + NewSQL := Batch.SQL; + // Replace all parameters by their values + // by descending to avoid having problems with similar variables name (eg test & test1) + for BindParam := tab.ListBindParams.Count-1 downto 0 do begin + // Do the Replace only if there is a value + if Length(tab.ListBindParams.Items[BindParam].Value)>0 then begin + NewSQL := StringReplace(NewSQL, + tab.ListBindParams.Items[BindParam].Name, + tab.ListBindParams.Items[BindParam].Value, + [rfReplaceAll]); + end; + end; + + Batch.SQL := NewSQL; + end; + + if AppSettings.ReadBool(asWarnUnsafeUpdates) and DoExecute then begin + ShowStatusMsg(_('Checking queries for unsafe UPDATEs/DELETEs ...')); + rx := TRegExpr.Create; + rx.ModifierI := True; + rx.Expression := '\sWHERE\s'; + ContainsUnsafeQueries := False; + for Query in Batch do begin + SQLNoComments := Query.SQLWithoutComments; + Command := UpperCase(getFirstWord(SQLNoComments)); + if ((Command = 'UPDATE') or (Command = 'DELETE')) and (not rx.Exec(SQLNoComments)) then begin + ContainsUnsafeQueries := True; + Break; + end; + end; + rx.Free; + if ContainsUnsafeQueries then begin + msg := _('Your query contains UPDATEs and/or DELETEs without a WHERE clause. Please confirm that you know what you''re doing.'); + DoExecute := MessageDialog(_('Run unsafe queries without a WHERE clause?'), msg, mtConfirmation, [mbYes, mbNo], asWarnUnsafeUpdates) = mrYes; + end; + end; + + + if DoExecute then begin + Screen.Cursor := crHourGlass; + EnableProgress(Batch.Count); + Tab.ResultTabs.Clear; + Tab.tabsetQuery.Tabs.Clear; + FreeAndNil(Tab.QueryProfile); + ProfileNode := FindNode(Tab.treeHelpers, TQueryTab.HelperNodeProfile, nil); + Tab.DoProfile := Assigned(ProfileNode) and (Tab.treeHelpers.CheckState[ProfileNode] in CheckedStates); + if Tab.DoProfile then try + ActiveConnection.Query('SET profiling=1'); + except + on E:EDbError do begin + ErrorDialog(f_('Query profiling requires %s or later, and the server must not be configured with %s.', ['MySQL 5.0.37', '--disable-profiling']), E.Message); + Tab.DoProfile := False; + end; + end; + + // Start the execution thread + Screen.Cursor := crAppStart; + ActiveConnection.Ping(True); // Prevents SynEdit paint exceptions if connection was killed outside + Tab.QueryRunning := True; + Tab.ExecutionThread := TQueryThread.Create(ActiveConnection, Batch, Tab.Number); + end; + + ValidateQueryControls(Sender); +end;} + + + +{procedure TMainForm.BeforeQueryExecution(Thread: TQueryThread); +begin + // Update GUI stuff + SetProgressPosition(Thread.BatchPosition); +end;} + + +{procedure TMainForm.AfterQueryExecution(Thread: TQueryThread); +var + Tab: TQueryTab; + NewTab: TResultTab; + col: TVirtualTreeColumn; + TabCaption, TabCaptions, BatchHead: String; + TabCaptionsList: TStringList; + TabsetColor: TColor; + Results: TDBQuery; + i, HeaderPadding, HeaderLineBreaks: Integer; +begin + // Single query or query packet has finished + + ShowStatusMsg(_('Setting up result grid(s) ...')); + Tab := QueryTabs.TabByNumber(Thread.TabNumber); + + // Use session color on result tabs + TabsetColor := Thread.Connection.Parameters.SessionColor; + if TabsetColor <> clNone then begin + Tab.tabsetQuery.SelectedColor := TabsetColor; + Tab.tabsetQuery.UnselectedColor := ColorAdjustLuma(TabsetColor, 20, False); + end + else begin + Tab.tabsetQuery.SelectedColor := clWindow; + Tab.tabsetQuery.UnselectedColor := clBtnFace; + end; + + // Get tab caption list from comment, similar to a name:xyz in a single query + BatchHead := Copy(Thread.Batch.SQL, 1, SIZE_KB); + TabCaptions := RegExprGetMatch('--\s+names\:\s*([^\r\n]+)', BatchHead, 1, False, True); + TabCaptionsList := Explode(',', TabCaptions); + LogSQL('TabCaptionsList: '+TabCaptionsList.CommaText, lcDebug); + // Create result tabs + for Results in Thread.Connection.GetLastResults do begin + NewTab := TResultTab.Create(Tab); + Tab.ResultTabs.Add(NewTab); + NewTab.Results := Results; + try + TabCaption := NewTab.Results.ResultName; + if TabCaption.IsEmpty and (TabCaptionsList.Count > NewTab.TabIndex) then + TabCaption := TabCaptionsList[NewTab.TabIndex]; + if TabCaption.IsEmpty then + TabCaption := NewTab.Results.TableName; + except + on E:EDbError do begin + TabCaption := _('Result')+' #'+IntToStr(NewTab.TabIndex+1); + end; + end; + TabCaption := Trim(TabCaption); + TabCaption := TabCaption + ' (' + FormatNumber(Results.RecordCount) + 'r × ' + FormatNumber(Results.ColumnCount) + 'c)'; + Tab.tabsetQuery.Tabs.Add(TabCaption); + NewTab.Grid.Name := Format('Tab%dGrid%d', [Tab.Number, NewTab.TabIndex+1]); + + NewTab.Grid.BeginUpdate; + NewTab.Grid.Header.Options := NewTab.Grid.Header.Options + [hoVisible]; + NewTab.Grid.Header.Columns.BeginUpdate; + NewTab.Grid.Header.Columns.Clear; + HeaderLineBreaks := 0; + HeaderPadding := NewTab.Grid.Header.Height - GetTextHeight(NewTab.Grid.Font); + col := NewTab.Grid.Header.Columns.Add; + col.CaptionAlignment := taRightJustify; + col.Alignment := taRightJustify; + col.Options := col.Options + [coFixed]- [coAllowClick, coAllowFocus, coEditable, coResizable]; + if not AppSettings.ReadBool(asShowRowId) then + col.Options := col.Options - [coVisible]; + col.Text := '#'; + for i:=0 to NewTab.Results.ColumnCount-1 do begin + col := NewTab.Grid.Header.Columns.Add; + col.Text := NewTab.Results.ColumnNames[i]; + if NewTab.Results.DataType(i).Category in [dtcInteger, dtcReal] then + col.Alignment := taRightJustify; + if NewTab.Results.ColIsPrimaryKeyPart(i) then + col.ImageIndex := ICONINDEX_PRIMARYKEY + else if NewTab.Results.ColIsUniqueKeyPart(i) then + col.ImageIndex := ICONINDEX_UNIQUEKEY + else if NewTab.Results.ColIsKeyPart(i) then + col.ImageIndex := ICONINDEX_INDEXKEY; + HeaderLineBreaks := Max(HeaderLineBreaks, col.text.CountChar(#10)); + end; + NewTab.Grid.Header.Columns.EndUpdate; + NewTab.Grid.Header.Height := GetTextHeight(NewTab.Grid.Font) * (HeaderLineBreaks+1) + HeaderPadding; + NewTab.Grid.RootNodeCount := NewTab.Results.RecordCount; + NewTab.Grid.EndUpdate; + for i:=0 to NewTab.Grid.Header.Columns.Count-1 do + AutoCalcColWidth(NewTab.Grid, i); + if Tab.tabsetQuery.TabIndex = -1 then + Tab.tabsetQuery.TabIndex := 0; + end; + ShowStatusMsg; +end;} + + +{procedure TMainForm.FinishedQueryExecution(Thread: TQueryThread); +var + Tab: TQueryTab; + MetaInfo, ErroneousSQL, RegName: String; + ProfileAllTime: Extended; + ProfileNode: PVirtualNode; + History: TQueryHistory; + HistoryItem: TQueryHistoryItem; + HistoryNum, RegItemsSize, KeepDays: Integer; + DoDelete, ValueFound: Boolean; + MinDate: TDateTime; + + procedure GoToErrorPos(Err: String); + var + rx: TRegExpr; + SelStart, ErrorPos: Integer; + ErrorCoord: TBufferCoord; + begin + // Try to set memo cursor to the relevant position + if Tab.LeftOffsetInMemo > 0 then + SelStart := Tab.LeftOffsetInMemo-1 + else + SelStart := Thread.Batch[Thread.BatchPosition].LeftOffset-1; + + // Extract erroneous portion of SQL out of error message + ErroneousSQL := ''; + rx := TRegExpr.Create; + rx.Expression := 'for the right syntax to use near ''(.+)'' at line (\d+)'; + if rx.Exec(Err) then + ErroneousSQL := rx.Match[1]; + rx.Expression := 'Duplicate entry ''([^'']+)'''; + if rx.Exec(Err) then + ErroneousSQL := rx.Match[1]; + rx.Free; + + if ErroneousSQL <> '' then begin + // Examine 1M of memo text at given offset + ErrorPos := Pos(ErroneousSQL, Copy(Tab.Memo.Text, SelStart, SIZE_MB)); + if ErrorPos > 0 then begin + Inc(SelStart, ErrorPos-1); + Tab.Memo.SelLength := 0; + Tab.Memo.SelStart := SelStart; + ErrorCoord := Tab.Memo.CharIndexToRowCol(SelStart); + Tab.ErrorLine := ErrorCoord.Line; + end; + end; + end; + +begin + // Find right query tab + Tab := QueryTabs.TabByNumber(Thread.TabNumber); + + // Error handling + if not Thread.ErrorMessage.IsEmpty then begin + SetProgressState(pbsError); + GoToErrorPos(Thread.ErrorMessage); + ErrorDialog(Thread.ErrorMessage); + end; + + // Gather meta info for logging + MetaInfo := _('Affected rows')+': '+FormatNumber(Thread.RowsAffected)+ + ' '+_('Found rows')+': '+FormatNumber(Thread.RowsFound)+ + ' '+_('Warnings')+': '+FormatNumber(Thread.WarningCount)+ + ' '+_('Duration for')+' ' + FormatNumber(Thread.BatchPosition); + if Thread.BatchPosition < Thread.Batch.Count then + MetaInfo := MetaInfo + ' ' + _('of') + ' ' + FormatNumber(Thread.Batch.Count); + if Thread.Batch.Count = 1 then + MetaInfo := MetaInfo + ' ' + _('query') + else + MetaInfo := MetaInfo + ' ' + _('queries'); + if Thread.QueryTime < 60*1000 then + MetaInfo := MetaInfo + ': '+FormatNumber(Thread.QueryTime/1000, 3) +' ' + _('sec.') + else + MetaInfo := MetaInfo + ': '+FormatTimeNumber(Thread.QueryTime/1000, True); + if Thread.QueryNetTime > 0 then + MetaInfo := MetaInfo + ' (+ '+FormatNumber(Thread.QueryNetTime/1000, 3) +' ' + _('sec.') + ' ' + _('network') + ')'; + LogSQL(MetaInfo); + + // Display query profile + if Tab.DoProfile then begin + Tab.QueryProfile := Thread.Connection.GetResults('SHOW PROFILE'); + Tab.ProfileTime := 0; + Tab.MaxProfileTime := 0; + while not Tab.QueryProfile.Eof do begin + ProfileAllTime := MakeFloat(Tab.QueryProfile.Col(1)); + Tab.ProfileTime := Tab.ProfileTime + ProfileAllTime; + Tab.MaxProfileTime := Max(Time, Tab.MaxProfileTime); + Tab.QueryProfile.Next; + end; + ProfileNode := FindNode(Tab.treeHelpers, TQueryTab.HelperNodeProfile, nil); + Tab.treeHelpers.ReinitNode(ProfileNode, True); + Tab.treeHelpers.InvalidateChildren(ProfileNode, True); + Thread.Connection.Query('SET profiling=0'); + end; + + // Store successful query packet in history if it's not a batch. + // Assume that a bunch of up to 5 queries is not a batch. + AppSettings.ResetPath; + if AppSettings.ReadBool(asQueryHistoryEnabled) + and Thread.ErrorMessage.IsEmpty + and (Thread.Batch.Count <= 5) + and (Thread.Batch.Size <= SIZE_MB) + then begin + ShowStatusMsg(_('Updating query history ...')); + KeepDays := AppSettings.ReadInt(asQueryHistoryKeepDays); + + // Load all items so we can clean up + History := TQueryHistory.Create(Thread.Connection.Parameters.SessionPath); + + // Find lowest unused item number + HistoryNum := 0; + while True do begin + Inc(HistoryNum); + RegName := IntToStr(HistoryNum); + ValueFound := False; + for HistoryItem in History do begin + if HistoryItem.RegValue = HistoryNum then begin + ValueFound := True; + Break; + end; + end; + if not ValueFound then + break; + end; + + // Delete identical history items to avoid spam + // Delete old items + // Delete items which exceed a max datasize barrier + AppSettings.SessionPath := Thread.Connection.Parameters.SessionPath + '\' + REGKEY_QUERYHISTORY; + MinDate := IncDay(Now, -KeepDays); + RegItemsSize := Thread.Batch.Size; + for HistoryItem in History do begin + Inc(RegItemsSize, Length(HistoryItem.SQL)); + DoDelete := (HistoryItem.SQL = Thread.Batch.SQL) + or (HistoryItem.Time < MinDate) + or (RegItemsSize > SIZE_MB); + if DoDelete then + AppSettings.DeleteValue(IntToStr(HistoryItem.RegValue)); + end; + History.Free; + + // Store history item and closing registry key to ensure writing has finished + try + AppSettings.WriteString(RegName, DateTimeToStr(Now) + DELIM + + Thread.Connection.Database + DELIM + + IntToStr(Thread.QueryTime+Thread.QueryNetTime) + DELIM + + Thread.Batch.SQL); + except + // Silence sporadic boring write errors. See http://www.heidisql.com/forum.php?t=13088 + on E:ERegistryException do + LogSQL(f_('Error when updating query history: %s', [E.Message]), lcError); + end; + + RefreshHelperNode(TQueryTab.HelperNodeHistory); + end; + + // Clean up + DisableProgress; + Tab.QueryRunning := False; + ValidateControls(Thread); + OperationRunning(False); + Screen.Cursor := crDefault; + ShowStatusMsg; +end;} + + +{procedure TMainForm.tabsetQueryClick(Sender: TObject); +var + QueryTab: TQueryTab; + i: Integer; +begin + // Result tab clicked / changed + Screen.Cursor := crHourGlass; + QueryTab := nil; + for i:=0 to QueryTabs.Count-1 do begin + if QueryTabs[i].tabsetQuery = Sender then begin + QueryTab := QueryTabs[i]; + break; + end; + end; + for i:=0 to QueryTab.ResultTabs.Count-1 do + QueryTab.ResultTabs[i].Grid.Hide; + if QueryTab.ActiveResultTab <> nil then begin + QueryTab.ActiveResultTab.Grid.Show; + // Reset filter if filter panel was disabled + UpdateFilterPanel(Sender); + end; + // Ensure controls are in a valid state + ValidateControls(Sender); + Screen.Cursor := crDefault; + ShowStatusMsg; +end;} + + +{procedure TMainForm.tabsetQueryGetImageIndex(Sender: TObject; TabIndex: Integer; + var ImageIndex: Integer); +begin + // Give result tabs of editable results a table icon + try + QueryTabs.ActiveTab.ResultTabs[TabIndex].Results.TableName; + ImageIndex := 14; + except + ImageIndex := -1; + end; +end;} + + +{procedure TMainForm.actExportDataExecute(Sender: TObject); +var + ExportDialog: TfrmExportGrid; +begin + // Save data in current dataset into various text file formats + ExportDialog := TfrmExportGrid.Create(Self); + ExportDialog.Grid := ActiveGrid; + ExportDialog.ShowModal; + ExportDialog.Free; +end;} + + +{procedure TMainForm.actDataPreviewUpdate(Sender: TObject); +var + Grid: TVirtualStringTree; +begin + // Enable or disable ImageView action + Grid := ActiveGrid; + (Sender as TAction).Enabled := (Grid <> nil) + and (Grid.FocusedColumn <> NoColumn) + and (GridResult(Grid).DataType(Grid.FocusedColumn-1).Category = dtcBinary) +end;} + + +{procedure TMainForm.actDataPreviewExecute(Sender: TObject); +var + MakeVisible: Boolean; +begin + // Show or hide preview area + actDataPreview.Checked := not actDataPreview.Checked; + MakeVisible := actDataPreview.Checked; + pnlPreview.Visible := MakeVisible; + spltPreview.Visible := MakeVisible; + if MakeVisible then + UpdatePreviewPanel; +end;} + + +{procedure TMainForm.UpdatePreviewPanel; +var + Grid: TVirtualStringTree; + Results: TDBQuery; + RowNum: PInt64; + ImgType: String; + Content, Header: AnsiString; + ContentStream: TMemoryStream; + StrLen, ResultCol: Integer; + Graphic: TGraphic; +begin + // Load BLOB contents into preview area + Grid := ActiveGrid; + Results := GridResult(Grid); + if not Assigned(Results) then + Exit; + ResultCol := Grid.FocusedColumn -1; + if ResultCol < 0 then + Exit; + Screen.Cursor := crHourGlass; + try + ShowStatusMsg(_('Loading contents into image viewer ...')); + lblPreviewTitle.Caption := _('Loading ...'); + lblPreviewTitle.Repaint; + imgPreview.Picture := nil; + AnyGridEnsureFullRow(Grid, Grid.FocusedNode); + RowNum := Grid.GetNodeData(Grid.FocusedNode); + Results.RecNo := RowNum^; + + Content := AnsiString(Results.Col(ResultCol)); + StrLen := Results.ColumnLengths(ResultCol); + ContentStream := TMemoryStream.Create; + ContentStream.Write(Content[1], StrLen); + ContentStream.Position := 0; + Graphic := nil; + ContentStream.Position := 0; + ImgType := 'UnknownType'; + Header := Copy(Content, 1, 50); + if Copy(Header, 7, 4) = 'JFIF' then begin + ImgType := 'JPEG'; + Graphic := TJPEGImage.Create; + end else if Copy(Header, 1, 3) = 'GIF' then begin + ImgType := 'GIF'; + Graphic := TGIFImage.Create; + end else if Copy(Header, 1, 2) = 'BM' then begin + ImgType := 'BMP'; + Graphic := TBitmap.Create; + end else if Copy(Header, 2, 3) = 'PNG' then begin + ImgType := 'PNG'; + Graphic := TPngImage.Create; + end; + if Assigned(Graphic) then begin + try + Graphic.LoadFromStream(ContentStream); + imgPreview.Picture.Graphic := Graphic; + lblPreviewTitle.Caption := ImgType+': '+ + IntToStr(Graphic.Width)+' x '+IntToStr(Graphic.Height)+' pixels, 100%, '+ + FormatByteNumber(StrLen); + spltPreview.OnMoved(spltPreview); + except + on E:Exception do + lblPreviewTitle.Caption := ImgType+': ' + E.Message + ' ('+E.ClassName+')'; + end; + FreeAndNil(ContentStream); + end else + lblPreviewTitle.Caption := f_('No image detected, %s', [FormatByteNumber(StrLen)]); + finally + lblPreviewTitle.Hint := lblPreviewTitle.Caption; + ShowStatusMsg; + Screen.Cursor := crDefault; + end; +end;} + + +{procedure TMainForm.pnlLeftResize(Sender: TObject); +var + WidthAvail: Integer; +begin + WidthAvail := editDatabaseFilter.Parent.Width - btnTreeFavorites.Width; + editDatabaseFilter.Left := 0; + editDatabaseFilter.Width := (WidthAvail div 2) - 1; + editTableFilter.Width := editDatabaseFilter.Width; + editTableFilter.Left := editDatabaseFilter.Width + 1; + btnTreeFavorites.Left := editTableFilter.Left + editTableFilter.Width + 1; + spltPreview.OnMoved(Sender); +end;} + + +{procedure TMainForm.spltPreviewMoved(Sender: TObject); +var + rx: TRegExpr; + ZoomFactorW, ZoomFactorH: Integer; +begin + // Do not overscale image so it's never zoomed to more than 100% + if (imgPreview.Picture.Graphic = nil) or (imgPreview.Picture.Graphic.Empty) then + Exit; + imgPreview.Stretch := (imgPreview.Picture.Width > imgPreview.Width) or (imgPreview.Picture.Height > imgPreview.Height); + ZoomFactorW := Trunc(Min(imgPreview.Picture.Width, imgPreview.Width) / imgPreview.Picture.Width * 100); + ZoomFactorH := Trunc(Min(imgPreview.Picture.Height, imgPreview.Height) / imgPreview.Picture.Height * 100); + rx := TRegExpr.Create; + rx.Expression := '(\D)(\d+%)';} + //lblPreviewTitle.Caption := rx.Replace(lblPreviewTitle.Caption, '${1}'+IntToStr(Min(ZoomFactorH, ZoomFactorW))+'%', true); + {lblPreviewTitle.Hint := lblPreviewTitle.Caption; + rx.Free; +end;} + + +{procedure TMainForm.actDataSaveBlobToFileExecute(Sender: TObject); +var + Grid: TVirtualStringTree; + Results: TDBQuery; + RowNum: PInt64; + Content: AnsiString; + FileStream: TFileStream; + StrLen: Integer; + Dialog: TSaveDialog; +begin + // Save BLOB to local file + Grid := ActiveGrid; + Results := GridResult(Grid); + Dialog := TSaveDialog.Create(Self); + Dialog.Filter := _('All files')+' (*.*)|*.*'; + Dialog.FileName := Results.ColumnOrgNames[Grid.FocusedColumn-1]; + if not (Results.DataType(Grid.FocusedColumn-1).Category in [dtcBinary, dtcSpatial]) then + Dialog.FileName := Dialog.FileName + '.txt'; + if Dialog.Execute then begin + Screen.Cursor := crHourGlass; + AnyGridEnsureFullRow(Grid, Grid.FocusedNode); + RowNum := Grid.GetNodeData(Grid.FocusedNode); + Results.RecNo := RowNum^; + if Results.DataType(Grid.FocusedColumn-1).Category in [dtcBinary, dtcSpatial] then + Content := AnsiString(Results.Col(Grid.FocusedColumn-1)) + else + Content := Utf8Encode(Results.Col(Grid.FocusedColumn-1)); + StrLen := Length(Content); + try + FileStream := TFileStream.Create(Dialog.FileName, fmCreate or fmOpenWrite); + FileStream.Write(Content[1], StrLen); + except on E:Exception do + ErrorDialog(E.Message); + end; + FreeAndNil(FileStream); + Screen.Cursor := crDefault; + end; + Dialog.Free; +end;} + + +{procedure TMainForm.actInsertFilesExecute(Sender: TObject); +var + Dialog: TfrmInsertFiles; +begin + Dialog := TfrmInsertFiles.Create(Self); + Dialog.ShowModal; + Dialog.Free; +end;} + +// Drop Table(s) +{procedure TMainForm.actDropObjectsExecute(Sender: TObject); +var + msg, db: String; + Node: PVirtualNode; + Obj: PDBObject; + DBObject: TDBObject; + ObjectList: TDBObjectList; + Editor: TDBObjectEditor; + Conn: TDBConnection; +begin + Conn := ActiveConnection; + + ObjectList := TDBobjectList.Create(TDBObjectDropComparer.Create, False); + + if DBTreeClicked(Sender) then begin + // drop table selected in tree view. + DBObject := ActiveDBObj; + case DBObject.NodeType of + lntDb: begin + if MessageDialog(f_('Drop Database "%s"?', [DBObject.Database]), f_('WARNING: You will lose all objects in database %s!', [DBObject.Database]), mtCriticalConfirmation, [mbok,mbcancel]) <> mrok then + Abort; + try + db := DBObject.Database; + Node := FindDBNode(DBtree, Conn, db); + SetActiveDatabase('', Conn); + Conn.Query(Conn.GetSQLSpecifity(spDatabaseDrop, [Conn.QuoteIdent(db)])); + DBtree.DeleteNode(Node); + Conn.ClearDbObjects(db); + Conn.RefreshAllDatabases; + InvalidateVT(ListDatabases, VTREE_NOTLOADED_PURGECACHE, False); + except + on E:EDbError do + ErrorDialog(E.Message); + end; + Exit; + end; + lntTable..lntEvent: ObjectList.Add(ActiveDBObj); + end; + end else begin + // Invoked from database tab + Node := GetNextNode(ListTables, nil, True); + while Assigned(Node) do begin + Obj := ListTables.GetNodeData(Node); + ObjectList.Add(Obj^); + Node := GetNextNode(ListTables, Node, True); + end; + end; + + // Fix actions temporarily enabled for popup menu. + ValidateControls(Sender); + + // Safety stop to avoid firing DROP TABLE without tablenames + if ObjectList.Count = 0 then + Exit; + + // Ask user for confirmation to drop selected objects + ObjectList.Sort; + msg := ''; + for DBObject in ObjectList do + msg := msg + DBObject.Name + ', '; + Delete(msg, Length(msg)-1, 2); + if MessageDialog(f_('Drop %d object(s) in database "%s"?', [ObjectList.Count, Conn.Database]), msg, mtCriticalConfirmation, [mbok,mbcancel]) = mrOk then begin + try + // Disable foreign key checks to avoid SQL errors + if Conn.Has(frForeignKeyChecksVar) then + Conn.Query('SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0'); + // Compose and run DROP [TABLE|VIEW|...] queries + Editor := ActiveObjectEditor; + for DBObject in ObjectList do begin + DBObject.Drop; + if Assigned(Editor) and Editor.Modified and Editor.DBObject.IsSameAs(DBObject) then + Editor.Modified := False; + end; + if Conn.Has(frForeignKeyChecksVar) then + Conn.Query('SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS'); + // Refresh ListTables + dbtree so the dropped tables are gone: + Conn.ClearDbObjects(ActiveDatabase); + RefreshTree; + SetActiveDatabase(Conn.Database, Conn); + except + on E:EDbError do + ErrorDialog(E.Message); + end; + ObjectList.Free; + end; +end;} + + +{procedure TMainForm.actLaunchCommandlineExecute(Sender: TObject); +var + path, p, log, cmd: String; + sep: Char; + Conn: TDBConnection; +begin + // Launch mysql.exe + Conn := ActiveConnection; + if not Conn.Parameters.IsAnyMySQL then + ErrorDialog(_('Command line only works on MySQL connections.')) + else begin + if IsWine then begin + cmd := 'mysql'; + sep := '/'; + end else begin + cmd := 'mysql.exe'; + sep := '\'; + end; + path := AppSettings.ReadString(asMySQLBinaries); + if (Length(path)>0) and (path[Length(path)] <> sep) then + path := path + sep; + if not FileExists(path+cmd, true) then begin + ErrorDialog(f_('You need to tell %s where your MySQL binaries reside, in %s > %s > %s.', [APPNAME, _('Tools'), _('Preferences'), _('General')])+ + CRLF+CRLF+f_('Current setting is: "%s"', [path])); + end else begin + p := ''; + if IsWine then begin + p := ' -e '+path+cmd; + path := ''; + cmd := '$TERM'; + end; + + log := path + cmd + p + Conn.Parameters.GetExternalCliArguments(Conn, nbTrue); + LogSQL(f_('Launching command line: %s', [log]), lcInfo); + + p := p + Conn.Parameters.GetExternalCliArguments(Conn, nbFalse); + ShellExec(cmd, path, p); + end; + end; +end;} + + +// Load SQL-file, make sure that SheetQuery is activated +{procedure TMainForm.actLoadSQLExecute(Sender: TObject); +var + i, ProceedResult: Integer; + Dialog: TExtFileOpenDialog; + Encoding: TEncoding; + Tab: TQueryTab; +begin + AppSettings.ResetPath; + Dialog := TExtFileOpenDialog.Create(Self); + Dialog.Options := Dialog.Options + [fdoAllowMultiSelect]; + Dialog.AddFileType('*.sql', _('SQL files')); + Dialog.AddFileType('*.*', _('All files')); + Dialog.DefaultExtension := 'sql'; + Dialog.Encodings.Assign(FileEncodings); + Dialog.EncodingIndex := AppSettings.ReadInt(asFileDialogEncoding, Self.Name); + if Dialog.Execute then begin + Encoding := GetEncodingByName(Dialog.Encodings[Dialog.EncodingIndex]); + if Encoding = nil then begin + ProceedResult := MessageDialog(_('Really auto-detect file encoding?') + SLineBreak + SLineBreak + + _('Auto detecting the encoding of a file is highly discouraged. You may experience data loss if the detection fails.') + SLineBreak + SLineBreak + + _('To avoid this message select the correct encoding before pressing Open.'), + mtConfirmation, [mbYes, mbCancel]); + end else begin + ProceedResult := mrYes; + end; + + if ProceedResult = mrYes then begin + if not RunQueryFiles(Dialog.Files, Encoding, Sender=actRunSQL) then begin + for i:=0 to Dialog.Files.Count-1 do begin + Tab := GetOrCreateEmptyQueryTab(False); + Tab.LoadContents(Dialog.Files[i], True, Encoding); + if i = Dialog.Files.Count-1 then + SetMainTab(Tab.TabSheet); + end; + end; + end; + AppSettings.WriteInt(asFileDialogEncoding, Dialog.EncodingIndex, Self.Name); + end; + Dialog.Free; +end;} + + +{function TMainForm.RunQueryFiles(Filenames: TStrings; Encoding: TEncoding; ForceRun: Boolean): Boolean; +var + i, FilesProcessed: Integer; + Filesize, FilesizeSum, CurrentPosition: Int64; + StartTime: UInt64; + msgtext: String; + AbsentFiles, PopupFileList: TStringList; + DoRunFiles: Boolean; + Dialog: TTaskDialog; + Btn: TTaskDialogButtonItem; + DialogResult: TModalResult; + Conn: TDBConnection; + ProgressDialog: IProgressDialog; + Dummy: Pointer; + TimeElapsed: Double; + RunSuccess: Boolean; +const + RunFileSize = 5*SIZE_MB; +begin + // Ask for execution when loading big files, or return false + Result := False; + + // Remove non existant files + AbsentFiles := TStringList.Create; + for i:=Filenames.Count-1 downto 0 do begin + if not FileExists(Filenames[i]) then begin + AbsentFiles.Add(Filenames[i]); + Filenames.Delete(i); + end; + end; + // Check if one or more files are large + DoRunFiles := ForceRun; + PopupFileList := TStringList.Create; + FilesizeSum := 0; + for i:=0 to Filenames.Count-1 do begin + FileSize := _GetFileSize(Filenames[i]); + Inc(FilesizeSum, Filesize); + PopupFileList.Add(ExtractFilename(Filenames[i]) + ' (' + FormatByteNumber(FileSize) + ')'); + DoRunFiles := DoRunFiles or (FileSize > RunFileSize); + end; + + if DoRunFiles then begin + if ForceRun then begin + // Don't ask, just run files + DialogResult := mrYes; + end else if (Win32MajorVersion >= 6) and StyleServices.Enabled then begin + Dialog := TTaskDialog.Create(Self); + Dialog.Caption := _('Opening large files'); + Dialog.Text := f_('Selected files have a size of %s', [FormatByteNumber(FilesizeSum, 1)]); + Dialog.ExpandButtonCaption := _('File list'); + Dialog.ExpandedText := PopupFileList.Text; + Dialog.Flags := [tfUseCommandLinks, tfExpandFooterArea]; + Dialog.CommonButtons := []; + Dialog.MainIcon := tdiWarning; + Btn := TTaskDialogButtonItem(Dialog.Buttons.Add); + Btn.Caption := _('Run file(s) directly'); + Btn.CommandLinkHint := _('... without loading into the editor'); + Btn.ModalResult := mrYes; + Btn := TTaskDialogButtonItem(Dialog.Buttons.Add); + Btn.Caption := _('Load file(s) into the editor'); + Btn.CommandLinkHint := _('Can cause large memory usage'); + Btn.ModalResult := mrNo; + Btn := TTaskDialogButtonItem(Dialog.Buttons.Add); + Btn.Caption := _('Cancel'); + Btn.ModalResult := mrCancel; + Dialog.Execute; + DialogResult := Dialog.ModalResult; + Dialog.Free; + end else begin + msgtext := f_('One or more of the selected files are larger than %s:', [FormatByteNumber(RunFileSize, 0)]) + CRLF + + Implode(CRLF, PopupFileList) + CRLF + CRLF + + _('Just run these files to avoid loading them into the query-editor (= memory)?') + CRLF + CRLF + + _('Press') + CRLF + + _(' [Yes] to run file(s) without loading it into the editor') + CRLF + + _(' [No] to load file(s) into the query editor') + CRLF + + _(' [Cancel] to cancel file opening.'); + DialogResult := MessageDialog(_('Execute query file(s)?'), msgtext, mtWarning, [mbYes, mbNo, mbCancel]); + end; + + case DialogResult of + mrYes: begin + Result := True; + // progress start + ProgressDialog := CreateComObject(CLSID_ProgressDialog) as IProgressDialog; + Dummy := nil; + CurrentPosition := 0; + FilesProcessed := 0; + StartTime := GetTickCount64; + Conn := ActiveConnection; + RunSuccess := False; + // PROGDLG_MODAL was used previously, but somehow that focuses some other application + ProgressDialog.StartProgressDialog(Handle, nil, PROGDLG_NOMINIMIZE or PROGDLG_AUTOTIME, Dummy); + for i:=0 to Filenames.Count-1 do begin + RunSuccess := RunQueryFile(Filenames[i], Encoding, Conn, ProgressDialog, FilesizeSum, CurrentPosition); + // Add filename to history menu + if Pos(AppSettings.DirnameSnippets, Filenames[i]) = 0 then + MainForm.AddOrRemoveFromQueryLoadHistory(Filenames[i], True, True); + Inc(FilesProcessed); + if not RunSuccess then + Break; + end; + // progress end + ProgressDialog.StopProgressDialog; + TimeElapsed := GetTickCount64 - StartTime; + LogSQL(f_('%s file(s) processed, in %s', [FormatNumber(FilesProcessed), FormatTimeNumber(TimeElapsed/1000, True)])); + if RunSuccess then + MessageBeep(MB_OK) + else + MessageBeep(MB_ICONERROR); + end; + mrNo: Result := False; + mrCancel: Result := True; + end; + end; + + if AbsentFiles.Count > 0 then + ErrorDialog(_('Could not load file(s):'), AbsentFiles.Text); + AbsentFiles.Free; + PopupFileList.Free; +end;} + + +{function TMainForm.RunQueryFile(FileName: String; Encoding: TEncoding; Conn: TDBConnection; + ProgressDialog: IProgressDialog; FilesizeSum: Int64; var CurrentPosition: Int64): Boolean; +var + Dummy: Pointer; + Stream: TFileStream; + Lines, LinesRemain, ErrorNotice: String; + Filesize, QueryCount, ErrorCount, RowsAffected: Int64; + Queries: TSQLBatch; + i: Integer; + + procedure StopProgress; + var + MessageText: String; + begin + ProgressDialog.SetLine(1, PChar(_('Clean up ...')), False, Dummy); + Queries.Free; + try + Stream.Free; + except; // Eat error when stream wasn't yet created properly + end; + // BringToFront; // Not sure why I added this initially, but it steals focus from other applications + if ProgressDialog.HasUserCancelled then + MessageText := 'File "%s" partially executed, with %s queries and %s affected rows' + else + MessageText := 'File "%s" executed, with %s queries and %s affected rows'; + LogSQL(f_(MessageText, [ExtractFileName(FileName), FormatNumber(QueryCount), FormatNumber(RowsAffected)])); + end; + +begin + // Import single SQL file and display progress dialog + ProgressDialog.SetTitle(PChar(f_('Importing file %s', [ExtractFileName(FileName)]))); + Dummy := nil; + + Result := True; + Lines := ''; + ErrorNotice := ''; + QueryCount := 0; + ErrorCount := 0; + RowsAffected := 0; + LinesRemain := ''; + Queries := TSQLBatch.Create; + + try + // Start file operations + Filesize := _GetFileSize(FileName); + + OpenTextfile(FileName, Stream, Encoding); + while Stream.Position < Stream.Size do begin + if ProgressDialog.HasUserCancelled then + Break; + + // Read lines from SQL file until buffer reaches a limit of some MB + // This strategy performs vastly better than looping through each line + ProgressDialog.SetLine(1, PChar(_('Reading next chunk from file...')), False, Dummy); + Lines := ReadTextfileChunk(Stream, Encoding, 20*SIZE_MB); + + // Split buffer into single queries + ProgressDialog.SetLine(1, PChar(_('Splitting queries...')), False, Dummy); + Queries.SQL := LinesRemain + Lines; + Lines := ''; + LinesRemain := ''; + + // Execute detected queries + for i:=0 to Queries.Count-1 do begin + if ProgressDialog.HasUserCancelled then + Break; + // Last line has to be processed in next loop if end of file is not reached + if (i = Queries.Count-1) and (Stream.Position < Stream.Size) then begin + LinesRemain := Queries[i].SQL; + Break; + end; + Inc(QueryCount); + Inc(CurrentPosition, Encoding.GetByteCount(Queries[i].SQL)); + if ErrorCount > 0 then + ErrorNotice := '(' + FormatNumber(ErrorCount) + ' ' + _('Errors') + ')'; + ProgressDialog.SetLine(1, + PChar(f_('Processing query #%s. %s', [FormatNumber(QueryCount), ErrorNotice])), + False, + Dummy + ); + ProgressDialog.SetLine(2, + PChar(f_('Position in file: %s / %s. Affected rows: %s.', [FormatByteNumber(CurrentPosition), FormatByteNumber(Filesize), FormatNumber(RowsAffected)])), + False, + Dummy + ); + ProgressDialog.SetProgress64(CurrentPosition, FilesizeSum); + + // Execute single query + // Break or don't break loop, depending on the state of "Stop on errors" button + try + Conn.Query(Queries[i].SQL, False, lcScript); + RowsAffected := RowsAffected + Conn.RowsAffected; + Conn.ShowWarnings; + except + on E:Exception do begin + if actQueryStopOnErrors.Checked then + raise + else + Inc(ErrorCount); + end; + end; + + end; + end; + if ProgressDialog.HasUserCancelled then begin + LogSQL(_('Cancelled by user')); + Result := False; + end; + StopProgress; + if ErrorCount > 0 then begin + ErrorDialog(_('Errors'), + f_('%s%% of your file has been processed, but there were %s errors when executing %s queries. Please check the SQL log panel for messages.', + [FormatNumber(100/FileSize*CurrentPosition, 0), FormatNumber(ErrorCount), FormatNumber(QueryCount)]) + ); + end; + + except + on E:Exception do begin + if (E is EFileStreamError) + or (E is EEncodingError) + or (E is EReadError) + then begin + StopProgress; + Result := False; + ErrorDialog(f_('Error while reading file "%s"', [FileName]), E.Message); + AddOrRemoveFromQueryLoadHistory(FileName, False, True); + end + else if E is EDbError then begin + StopProgress; + Result := False; + ErrorDialog(E.Message + CRLF + CRLF + + f_('Notice: You can disable the "%s" option to ignore such errors', [actQueryStopOnErrors.Caption]) + ); + end + else begin + raise; + end; + end; + end; +end;} + + +{procedure TMainForm.SessionConnect(Sender: TObject); +var + SessionPath: String; + Connection: TDBConnection; + Params: TConnectionParameters; + Node, SessionNode: PVirtualNode; + DBObj: PDBObject; + i: Integer; +begin + // Click on quick-session menu item: + SessionPath := StripHotkey((Sender as TMenuItem).Caption); + Node := nil; + // Probably wanted session was clicked before: navigate to last node + for i:=High(FTreeClickHistory) downto Low(FTreeClickHistory) do begin + if FTreeClickHistory[i] <> nil then begin + DBObj := DBtree.GetNodeData(FTreeClickHistory[i]); + if DBObj = nil then // Session disconnected + Break; + if DBObj.Connection.Parameters.SessionPath = SessionPath then begin + Node := FTreeClickHistory[i]; + break; + end; + end; + end; + if not Assigned(Node) then begin + // Wanted session was not clicked yet but probably connected: navigate to root node + SessionNode := DBtree.GetFirstChild(nil); + while Assigned(SessionNode) do begin + DBObj := DBtree.GetNodeData(SessionNode); + if DBObj.Connection.Parameters.SessionPath = SessionPath then begin + Node := SessionNode; + end; + SessionNode := DBtree.GetNextSibling(SessionNode); + end; + end; + // Finally we have a node if session is already connected + if Assigned(Node) then + SelectNode(DBtree, Node) + else begin + Params := TConnectionParameters.Create(SessionPath); + InitConnection(Params, True, Connection); + end; +end;} + + +{** + Receive connection parameters and create a connection tree node + Paremeters are either sent by connection-form or by commandline. +} +{function TMainform.InitConnection(Params: TConnectionParameters; ActivateMe: Boolean; var Connection: TDBConnection): Boolean; +var + RestoreLastActiveDatabase: Boolean; + StartupScript, LastActiveDatabase: String; + StartupBatch: TSQLBatch; + Query: TSQLSentence; + SessionNode, DBNode: PVirtualNode; +begin + Connection := Params.CreateConnection(Self); + Connection.OnLog := LogSQL; + Connection.OnConnected := ConnectionReady; + Connection.OnDatabaseChanged := DatabaseChanged; + Connection.OnObjectnamesChanged := ObjectnamesChanged; + try + Connection.Active := True; + // We have a connection + Result := True; + FConnections.Add(Connection); + + if AppSettings.SessionPathExists(Params.SessionPath) then begin + // Save "connected" counter + AppSettings.SessionPath := Params.SessionPath; + AppSettings.WriteInt(asConnectCount, AppSettings.ReadInt(asConnectCount)+1); + // Save server version + AppSettings.WriteInt(asServerVersion, Connection.ServerVersionInt); + AppSettings.WriteString(asLastConnect, DateTimeToStr(Now)); + end; + + if ActivateMe then begin + // Set focus on last uses db. If not wanted or db is gone, go to root node at least + RestoreLastActiveDatabase := AppSettings.ReadBool(asRestoreLastUsedDB); + AppSettings.SessionPath := Params.SessionPath; + LastActiveDatabase := AppSettings.ReadString(asLastUsedDB); + if RestoreLastActiveDatabase + and (Connection.AllDatabases.IndexOf(LastActiveDatabase) >- 1) + and (Connection.GetLockedTableCount(LastActiveDatabase) = 0) + then begin + SetActiveDatabase(LastActiveDatabase, Connection); + DBNode := FindDBNode(DBtree, Connection, LastActiveDatabase); + if Assigned(DBNode) then + DBtree.Expanded[DBNode] := True; + end else begin + SessionNode := GetRootNode(DBtree, Connection); + SelectNode(DBtree, SessionNode); + DBtree.Expanded[SessionNode] := True; + end; + end; + + // Process startup script + StartupScript := Trim(Connection.Parameters.StartupScriptFilename); + if StartupScript <> '' then begin + StartupScript := ExpandFileName(StartupScript); + if not FileExists(StartupScript) then + ErrorDialog(f_('Startup script file not found: %s', [StartupScript])) + else begin + StartupBatch := TSQLBatch.Create; + StartupBatch.SQL := ReadTextfile(StartupScript, nil); + for Query in StartupBatch do try + Connection.Query(Query.SQL); + except + // Suppress popup, errors get logged into SQL log + end; + StartupBatch.Free; + end; + end; + + if Params.WantSSL and not Connection.IsSSL then begin + MessageDialog(_('SSL not used.'), + _('Your SSL settings were not accepted by the server, or the server does not support any SSL configuration.'), + mtWarning, + [mbOK], + asSSLWarnUnused + ); + end; + + // Apply favorite object paths + AppSettings.SessionPath := Params.SessionPath; + Connection.Favorites.Text := AppSettings.ReadString(asFavoriteObjects); + actFavoriteObjectsOnly.Checked := False; + + // Tree node filtering needs a hit once when connected + editDatabaseTableFilterChange(Self); + + except + on E:EDbError do begin + MessageDialog(_('Connection failed'), E.Message, mtError, [mbOK], asUnused, E.Hint); + // attempt failed + if AppSettings.SessionPathExists(Params.SessionPath) then begin + // Save "refused" counter + AppSettings.SessionPath := Params.SessionPath; + AppSettings.WriteInt(asRefusedCount, AppSettings.ReadInt(asRefusedCount)+1); + end; + Result := False; + FreeAndNil(Connection); + end; + end; + + StoreLastSessions; + ValidateControls(Connection); + ShowStatusMsg; +end;} + + +{procedure TMainForm.actDataDeleteExecute(Sender: TObject); +var + Grid: TVirtualStringTree; + Node, FocusAfterDelete: PVirtualNode; + RowNum: PInt64; + Results: TDBQuery; + Nodes: TNodeArray; + i: Integer; +begin + // Delete row(s) + Grid := ActiveGrid; + Results := GridResult(Grid); + if Grid.SelectedCount = 0 then + ErrorDialog(_('No rows selected'), _('Please select one or more rows to delete them.')) + else try + Results.CheckEditable; + if MessageDialog(f_('Delete %s row(s)?', [FormatNumber(Grid.SelectedCount)]), + mtConfirmation, [mbOK, mbCancel]) = mrOK then begin + FocusAfterDelete := nil; + EnableProgress(Grid.SelectedCount); + Node := GetNextNode(Grid, nil, True); + while Assigned(Node) do begin + RowNum := Grid.GetNodeData(Node); + ShowStatusMsg(f_('Deleting row #%s of %s ...', [FormatNumber(ProgressBarStatus.Position+1), FormatNumber(ProgressBarStatus.Max)])); + Results.RecNo := RowNum^; + Results.DeleteRow; + ProgressStep; + SetLength(Nodes, Length(Nodes)+1); + Nodes[Length(Nodes)-1] := Node; + FocusAfterDelete := Node; + Node := GetNextNode(Grid, Node, True); + end; + ShowStatusMsg(_('Clean up ...')); + if Assigned(FocusAfterDelete) then + FocusAfterDelete := Grid.GetNext(FocusAfterDelete); + // Remove nodes and select some nearby node + Grid.BeginUpdate; + for i:=Low(Nodes) to High(Nodes) do + Grid.DeleteNode(Nodes[i]); + Grid.EndUpdate; + if not Assigned(FocusAfterDelete) then + FocusAfterDelete := Grid.GetLast; + if Assigned(FocusAfterDelete) then + SelectNode(Grid, FocusAfterDelete); + DisplayRowCountStats(Grid); + ValidateControls(Sender); + end; + except on E:EDbError do begin + SetProgressState(pbsError); + ErrorDialog(_('Grid editing error'), E.Message); + end; + end; + DisableProgress; + ShowStatusMsg(); +end;} + + +{procedure TMainForm.actUpdateCheckExecute(Sender: TObject); +var + frm : TfrmUpdateCheck; +begin + frm := TfrmUpdateCheck.Create(Self); + frm.ShowModal; + frm.Free; // FormClose has no caFree, as it may not have been called +end;} + + +{procedure TMainForm.actCreateDBObjectExecute(Sender: TObject); +var + Obj: TDBObject; + a: TAction; +begin + // Create a new table, view, etc. + FFocusedTables := GetFocusedObjects(Sender, [lntTable]); + tabEditor.TabVisible := True; + SetMainTab(tabEditor); + a := Sender as TAction; + Obj := TDBObject.Create(ActiveConnection); + Obj.Database := ActiveDatabase; + if a = actCreateTable then Obj.NodeType := lntTable + else if a = actCreateView then Obj.NodeType := lntView + else if a = actCreateProcedure then Obj.NodeType := lntProcedure + else if a = actCreateTrigger then Obj.NodeType := lntTrigger + else if a = actCreateEvent then Obj.NodeType := lntEvent + else if a = actCreateFunction then Obj.NodeType := lntFunction; + + PlaceObjectEditor(Obj); +end;} + + +{procedure TMainForm.actEmptyTablesExecute(Sender: TObject); +var + TableOrView: TDBObject; + Objects: TDBObjectList; + Names, QueryDisableChecks, QueryEnableChecks: String; + Conn: TDBConnection; + Dialog: TTaskDialog; + DialogResult: TModalResult; + DisableForeignKeyChecks: Boolean; +begin + // Delete rows from selected tables and views + + // See issue #3166 + if (not DBtree.Focused) and (not ListTables.Focused) then + Exit; + + Objects := GetFocusedObjects(Sender, [lntTable, lntView]); + for TableOrView in Objects do begin + Names := Names + TableOrView.Name + ', '; + end; + Delete(Names, Length(Names)-1, 2); + + if Objects.Count = 0 then + ErrorDialog(_('No table(s) selected.')) + else begin + Conn := ActiveConnection; + QueryDisableChecks := Conn.GetSQLSpecifity(spDisableForeignKeyChecks); + QueryEnableChecks := Conn.GetSQLSpecifity(spEnableForeignKeyChecks); + if (Win32MajorVersion >= 6) and StyleServices.Enabled then begin + Dialog := TTaskDialog.Create(Self); + Dialog.Text := f_('Empty %d table(s) and/or view(s)?', [Objects.count]); + Dialog.CommonButtons := [tcbOk, tcbCancel]; + Dialog.Flags := Dialog.Flags + [tfUseHiconMain]; + Dialog.CustomMainIcon := ConfirmIcon; + if not QueryDisableChecks.IsEmpty then + Dialog.VerificationText := _('Disable foreign key checks'); + Dialog.Execute; + DialogResult := Dialog.ModalResult; + DisableForeignKeyChecks := tfVerificationFlagChecked in Dialog.Flags; + Dialog.Free; + end else begin + DialogResult := MessageDialog(f_('Empty %d table(s) and/or view(s)?', [Objects.count]), Names, mtConfirmation, [mbOk, mbCancel]); + DisableForeignKeyChecks := False; + end; + if DialogResult = mrOk then begin + Screen.Cursor := crHourglass; + EnableProgress(Objects.Count); + try + if DisableForeignKeyChecks and (not QueryDisableChecks.IsEmpty) then + Conn.Query(QueryDisableChecks); + try + for TableOrView in Objects do begin + Conn.Query(Conn.GetSQLSpecifity(spEmptyTable) + TableOrView.QuotedName); + ProgressStep; + end; + actRefresh.Execute; + except + on E:EDbError do begin + SetProgressState(pbsError); + ErrorDialog(E.Message); + end; + end; + if DisableForeignKeyChecks and (not QueryEnableChecks.IsEmpty) then + Conn.Query(QueryEnableChecks); + except + on E:EDbError do + ErrorDialog(E.Message); + end; + Objects.Free; + DisableProgress; + Screen.Cursor := crDefault; + end; + end; +end;} + + +{procedure TMainForm.actBatchInOneGoExecute(Sender: TObject); +begin + // +end;} + + +{function TMainForm.DBTreeClicked(Sender: TObject): Boolean; +begin + // Find out if user rightclicked in tree or in database tab, + // which is a bit complex, so outsourced here. + Result := DBTree.Focused + or (PageControlMain.ActivePage <> tabDatabase) + or (PopupComponent(Sender) = DBtree); +end;} + + +{function TMainForm.GetFocusedObjects(Sender: TObject; NodeTypes: TListNodeTypes): TDBObjectList; +var + Node: PVirtualNode; + pObj: PDBObject; +begin + // Return list of selected database objects in current area + Result := TDBObjectList.Create(False); + + if DBTreeClicked(Sender) then begin + if ActiveDbObj.NodeType in NodeTypes then + Result.Add(ActiveDbObj); + end else begin + Node := GetNextNode(ListTables, nil, True); + while Assigned(Node) do begin + pObj := ListTables.GetNodeData(Node); + if pObj.NodeType in NodeTypes then + Result.Add(pObj^); + Node := GetNextNode(ListTables, Node, True); + end; + end; +end;} + + +{procedure TMainForm.actRunRoutinesExecute(Sender: TObject); +var + Tab: TQueryTab; + Query, ParamValues, ParamValue: String; + Params: TStringList; + Obj: TDBObject; + Objects: TDBObjectList; + Parameters: TRoutineParamList; + Param: TRoutineParam; + Cancelled: Boolean; +begin + // Run stored function(s) or procedure(s) + Objects := GetFocusedObjects(Sender, [lntProcedure, lntFunction]); + + if Objects.Count = 0 then + ErrorDialog(_('No stored procedure selected.'), _('Please select one or more stored function(s) or routine(s).')); + + for Obj in Objects do begin + actNewQueryTab.Execute; + Tab := QueryTabs[MainForm.QueryTabs.Count-1]; + case Obj.Connection.Parameters.NetTypeGroup of + ngMySQL: + case Obj.NodeType of + lntProcedure: Query := 'CALL '; + lntFunction: Query := 'SELECT '; + end; + ngMSSQL: + Query := 'EXEC '; + ngPgSQL: + Query := 'SELECT '; + else + raise Exception.CreateFmt(_(MsgUnhandledNetType), [Integer(Obj.Connection.Parameters.NetType)]); + end; + Parameters := TRoutineParamList.Create; + Obj.Connection.ParseRoutineStructure(Obj, Parameters); + Query := Query + Obj.QuotedName; + Cancelled := False; + Params := TStringList.Create; + for Param in Parameters do begin + ParamValue := ''; + if not InputQuery(Obj.Name, _('Parameter')+' "'+Param.Name+'" ('+Param.Datatype+')', ParamValue) then begin + Cancelled := True; + Break; + end; + ParamValue := Obj.Connection.EscapeString(ParamValue); + Params.Add(ParamValue); + end; + if not Cancelled then begin + Parameters.Free; + ParamValues := ''; + case Obj.Connection.Parameters.NetTypeGroup of + ngMySQL, ngPgSQL: + ParamValues := '(' + Implode(', ', Params) + ')'; + ngMSSQL: + ParamValues := ' ' + Implode(' ', Params); + else + raise Exception.CreateFmt(_(MsgUnhandledNetType), [Integer(Obj.Connection.Parameters.NetType)]); + end; + Query := Query + ParamValues; + Tab.Memo.Text := Query; + actExecuteQueryExecute(Sender); + end; + // Also cancel the whole loop over multiple procedures + if Cancelled then + Break; + end; +end;} + + +{procedure TMainForm.actNewWindowExecute(Sender: TObject); +begin + ShellExec( ExtractFileName(paramstr(0)), ExtractFilePath(paramstr(0)) ); +end;} + + +{procedure TMainForm.actQueryFindReplaceExecute(Sender: TObject); +var + OldDataLocalNumberFormat: Boolean; +begin + // Display search + replace dialog + if not Assigned(FSearchReplaceDialog) then + FSearchReplaceDialog := TfrmSearchReplace.Create(Self); + if FSearchReplaceDialog.Visible then + Exit; + FSearchReplaceDialog.chkReplace.Checked := Sender = actQueryReplace; + if (ActiveSynMemo(False) <> nil) or (ActiveGrid <> nil) then begin + OldDataLocalNumberFormat := DataLocalNumberFormat; + DataLocalNumberFormat := False; + FSearchReplaceDialog.ShowModal; + DataLocalNumberFormat := OldDataLocalNumberFormat; + ValidateControls(Sender); + end; +end;} + + +{procedure TMainForm.actQueryFindAgainExecute(Sender: TObject); +var + NeedDialog: Boolean; + Editor: TSynMemo; + Grid: TVirtualStringTree; + OldDataLocalNumberFormat: Boolean; +begin + // F3 - search or replace again, using previous settings + NeedDialog := not Assigned(FSearchReplaceDialog); + if Assigned(FSearchReplaceDialog) then begin + NeedDialog := NeedDialog or ((FSearchReplaceDialog.Grid = nil) and (FSearchReplaceDialog.Editor = nil)); + Editor := ActiveSynMemo(False); + Grid := ActiveGrid; + NeedDialog := NeedDialog or ((Grid = nil) and (Editor = nil)); + if (Editor <> nil) and (Editor.Focused) then + NeedDialog := NeedDialog or (FSearchReplaceDialog.Editor<>Editor); + if (Grid <> nil) and (Grid.Focused) then + NeedDialog := NeedDialog or (FSearchReplaceDialog.Grid<>Grid); + end; + + if NeedDialog then + actQueryFindReplaceExecute(Sender) + else begin + OldDataLocalNumberFormat := DataLocalNumberFormat; + DataLocalNumberFormat := False; + Exclude(FSearchReplaceDialog.Options, ssoEntireScope); + FSearchReplaceDialog.DoSearchReplace(Sender); + DataLocalNumberFormat := OldDataLocalNumberFormat; + end; +end;} + + +{procedure TMainForm.SynMemoQueryReplaceText(Sender: TObject; const ASearch, + AReplace: string; Line, Column: Integer; var Action: TSynReplaceAction); +begin + // Fires when "Replace all" in search dialog was pressed with activated "Prompt on replace" + case MessageDialog(f_('Replace this occurrence of "%s"?', [StrEllipsis(ASearch, 100)]), mtConfirmation, [mbYes, mbYesToAll, mbNo, mbCancel]) of + mrYes: Action := raReplace; + mrYesToAll: Action := raReplaceAll; + mrNo: Action := raSkip; + mrCancel: Action := raCancel; + end; +end;} + + +{procedure TMainForm.actRefreshExecute(Sender: TObject); +var + tab1, tab2: TTabSheet; + List: TVirtualStringTree; + OldDbObject: TDBObject; +begin + // Refresh + // Force data tab update when appropriate. + // Disable refresh action and re-enable in ApplicationOnIdle event + tab1 := PageControlMain.ActivePage; + actRefresh.Enabled := False; + FRefreshActionDisabledAt := GetTickCount; + if ActiveControl = DBtree then + RefreshTree + else if tab1 = tabHost then begin + tab2 := PageControlHost.ActivePage; + if tab2 = tabDatabases then + List := ListDatabases + else if tab2 = tabVariables then + List := ListVariables + else if tab2 = tabStatus then + List := ListStatus + else if tab2 = tabProcessList then + List := ListProcesses + else + List := ListCommandStats; + InvalidateVT(List, VTREE_NOTLOADED_PURGECACHE, True); + end else if tab1 = tabDatabase then begin + OldDbObject := TDBObject.Create(FActiveDbObj.Connection); + OldDbObject.Assign(FActiveDbObj); + RefreshTree(OldDbObject); + end else if tab1 = tabEditor then begin + RefreshTree; + end else if tab1 = tabData then + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); +end;} + + +{procedure TMainForm.actSQLhelpExecute(Sender: TObject); +var + keyword: String; + Tree: TVirtualStringTree; + SynMemo: TSynMemo; +begin + // Call SQL Help from various places + keyword := ''; + + // Query-Tab + if ActiveControl is TSynMemo then begin + SynMemo := TSynMemo(ActiveControl); + keyword := SynMemo.WordAtCursor; + if keyword.IsEmpty then begin + keyword := SynMemo.SelText; + end; + end + + // Data-Tab + else if (PageControlMain.ActivePage = tabData) + and Assigned(DataGrid.FocusedNode) then begin + keyword := SelectedTableFocusedColumn.DataType.Name; + + end else if ActiveControl = QueryTabs.ActiveHelpersTree then begin + // Makes only sense if one of the nodes "SQL fn" or "SQL kw" was selected + Tree := QueryTabs.ActiveHelpersTree; + if Assigned(Tree.FocusedNode) + and (Tree.GetNodeLevel(Tree.FocusedNode)=1) + and (Tree.FocusedNode.Parent.Index in [TQueryTab.HelperNodeFunctions, TQueryTab.HelperNodeKeywords]) then + keyword := Tree.Text[Tree.FocusedNode, 0]; + end; + + // Clean existing paranthesis, fx: char(64) + if Pos( '(', keyword ) > 0 then + keyword := Copy( keyword, 1, Pos( '(', keyword )-1 ); + + // Show the window + CallSQLHelpWithKeyword( keyword ); +end;} + + +{procedure TMainForm.actSynEditCompletionProposeExecute(Sender: TObject); +begin + // Show completion proposal explicitely, without the use of its own ShortCut property, + // to support a customized shortcut, see + SynCompletionProposal.Editor := ActiveSynMemo(False); + if Screen.ActiveControl is TCustomSynEdit then + SynCompletionProposal.ActivateCompletion + else + MessageBeep(MB_ICONEXCLAMATION); +end;} + +{procedure TMainForm.actSynMoveDownExecute(Sender: TObject); +var + Editor: TSynMemo; +begin + // Move line of text one down + Editor := ActiveSynMemo(False); + if Assigned(Editor) and (Editor.CaretY < Editor.Lines.Count) then begin + //Logsql('Editor.CaretY:'+Editor.CaretY.ToString+' Editor.Lines.Count:'+Editor.Lines.Count.ToString); + Editor.Lines.Exchange(Editor.CaretY-1, Editor.CaretY); + Editor.CaretY := Editor.CaretY + 1; + // OnStatusChanged implicitly fired here + if Assigned(Editor.OnChange) then + Editor.OnChange(Editor); + Editor.Repaint; + end else begin + MessageBeep(MB_ICONERROR); + end; +end;} + +{procedure TMainForm.actSynMoveUpExecute(Sender: TObject); +var + Editor: TSynMemo; +begin + // Move line of text one up + Editor := ActiveSynMemo(False); + if Assigned(Editor) and (Editor.CaretY >= 2) then begin + Editor.Lines.Exchange(Editor.CaretY-1, Editor.CaretY-2); + Editor.CaretY := Editor.CaretY - 1; + // OnStatusChanged implicitly fired here + if Assigned(Editor.OnChange) then + Editor.OnChange(Editor); + Editor.Repaint; + end else begin + MessageBeep(MB_ICONERROR); + end; +end;} + +{*** + Show SQL Help window directly using a keyword + @param String SQL-keyword + @see FieldeditForm.btnDatatypeHelp +} +{procedure TMainform.CallSQLHelpWithKeyword( keyword: String ); +begin + if FActiveDbObj.Connection.Has(frHelpKeyword) then begin + if not Assigned(SqlHelpDialog) then + SqlHelpDialog := TfrmSQLhelp.Create(Self); + SqlHelpDialog.Show; + SqlHelpDialog.Keyword := keyword; + end else + ErrorDialog(_('SQL help not available.'), f_('HELP <keyword> requires %s or newer.', ['MySQL 4.1'])); +end;} + + +{procedure TMainForm.actSaveSynMemoToTextfileExecute(Sender: TObject); +var + Comp: TComponent; + Memo: TSynMemo; + Dialog: TExtFileSaveDialog; +begin + // Save to textfile, from any TSynMemo (SQL log, "CREATE code" tab in editor, ...) + Memo := nil; + // Try to find memo from menu item's popup component, and if that fails, check ActiveControl. + // See #353 + Comp := PopupComponent(Sender); + if Comp is TSynMemo then + Memo := Comp as TSynMemo + else if ActiveControl is TSynMemo then + Memo := ActiveControl as TSynMemo; + if Assigned(Memo) then begin + Dialog := TExtFileSaveDialog.Create(Self); + Dialog.Options := Dialog.Options + [fdoOverWritePrompt]; + Dialog.AddFileType('*.sql', _('SQL files')); + Dialog.AddFileType('*.*', _('All files')); + Dialog.DefaultExtension := 'sql'; + Dialog.LineBreakIndex := TLineBreaks(AppSettings.ReadInt(asLineBreakStyle)); + if Dialog.Execute then begin + Screen.Cursor := crHourGlass; + SaveUnicodeFile( + Dialog.FileName, + Implode(GetLineBreak(Dialog.LineBreakIndex), Memo.Lines), + UTF8NoBOMEncoding + ); + Screen.Cursor := crDefault; + end; + end else begin + ErrorDialog(f_('No SQL editor focused. ActiveControl is %s', [ActiveControl.Name])); + end; +end;} + + +{procedure TMainForm.actSaveSQLAsExecute(Sender: TObject); +var + i: Integer; + CanSave: TModalResult; + OnlySelection: Boolean; + Dialog: TExtFileSaveDialog; + QueryTab: TQueryTab; + DefaultFilename: String; +begin + // Save SQL + CanSave := mrNo; + QueryTab := QueryTabs.ActiveTab; + Dialog := TExtFileSaveDialog.Create(Self); + if QueryTab.MemoFilename.IsEmpty then + DefaultFilename := QueryTab.TabSheet.Caption + else + DefaultFilename := ExtractFileName(QueryTab.MemoFilename); + DefaultFilename := DefaultFilename.Trim([' ', '*']); + Dialog.FileName := ValidFilename(DefaultFilename); + Dialog.Options := Dialog.Options + [fdoOverwritePrompt]; + if (Sender = actSaveSQLSnippet) or (Sender = actSaveSQLSelectionSnippet) then begin + Dialog.DefaultFolder := AppSettings.DirnameSnippets; + Dialog.Options := Dialog.Options + [fdoNoChangeDir]; + Dialog.Title := _('Save snippet'); + end; + Dialog.AddFileType('*.sql', _('SQL files')); + Dialog.AddFileType('*.*', _('All files')); + Dialog.DefaultExtension := 'sql'; + Dialog.LineBreakIndex := QueryTab.MemoLineBreaks; + while (CanSave = mrNo) and Dialog.Execute do begin + // Save complete content or just the selected text, + // depending on the tag of calling control + CanSave := mrYes; + for i:=0 to QueryTabs.Count-1 do begin + if QueryTabs[i].MemoFilename = Dialog.FileName then begin + CanSave := MessageDialog(f_('Overwrite "%s"?', [Dialog.FileName]), f_('This file is already open in query tab #%d.', [QueryTabs[i].Number]), + mtWarning, [mbYes, mbNo, mbCancel]); + break; + end; + end; + end; + if CanSave = mrYes then begin + OnlySelection := (Sender = actSaveSQLselection) or (Sender = actSaveSQLSelectionSnippet); + QueryTab.MemoLineBreaks := Dialog.LineBreakIndex; + QueryTab.SaveContents(Dialog.FileName, OnlySelection); + for i:=0 to QueryTabs.Count-1 do begin + if QueryTabs[i] = QueryTab then + continue; + if QueryTabs[i].MemoFilename = Dialog.FileName then + QueryTabs[i].Memo.Modified := True; + end; + ValidateQueryControls(Sender); + SetSnippetFilenames; + end; + Dialog.Free; +end;} + + +{procedure TMainForm.actSaveSQLExecute(Sender: TObject); +var + i: Integer; + ObjEditor: TDBObjectEditor; + Handled: Boolean; +begin + Handled := False; + if QueryTabs.HasActiveTab then begin + // Save SQL tab contents to file + if QueryTabs.ActiveTab.MemoFilename <> '' then begin + QueryTabs.ActiveTab.SaveContents(QueryTabs.ActiveTab.MemoFilename, False); + for i:=0 to QueryTabs.Count-1 do begin + if QueryTabs[i] = QueryTabs.ActiveTab then + continue; + if QueryTabs[i].MemoFilename = QueryTabs.ActiveTab.MemoFilename then + QueryTabs[i].Memo.Modified := True; + end; + ValidateQueryControls(Sender); + end else + actSaveSQLAsExecute(Sender); + Handled := True; + end + else if PageControlMain.ActivePage = tabEditor then begin + // Save table, procedure, etc. + ObjEditor := ActiveObjectEditor; + if Assigned(ObjEditor) and ObjEditor.Modified then begin + ObjEditor.ApplyModifications; + Handled := True; + end; + end; + if not Handled then begin + MessageBeep(MB_ICONASTERISK); + end; + +end;} + + +{procedure TMainForm.actQueryStopOnErrorsExecute(Sender: TObject); +begin + // Weird fix: dummy routine to avoid the sending action getting disabled +end;} + + +{procedure TMainForm.actQueryWordWrapExecute(Sender: TObject); +begin + // SetupSynEditors applies all customizations to any SynEditor + if (Sender as TAction).Checked then + actCodeFolding.Checked := False; + SetupSynEditors; +end;} + + +{procedure TMainForm.actCodeFoldingExecute(Sender: TObject); +begin + // Activates code folding + // Wordwrap does not work in conjunction with code folding. + // See https://github.com/SynEdit/SynEdit/blob/master/CodeFolding.md + if (Sender as TAction).Checked then + actQueryWordWrap.Checked := False; + SetupSynEditors; +end;} + + +{procedure TMainForm.actCodeFoldingStartRegionExecute(Sender: TObject); +var + Memo: TSynMemo; +begin + // Insert #region + if not actCodeFolding.Checked then + actCodeFolding.Execute; + Memo := ActiveSynMemo(False); + Memo.InsertLine(Memo.CaretXY, Memo.CaretXY, '#region ', True); +end;} + + +{procedure TMainForm.actConnectionPropertiesExecute(Sender: TObject); +var + Conn: TDBConnection; + i: Integer; + Infos: TStringList; + InfoText: String; +begin + Conn := ActiveConnection; + if Conn <> nil then begin + Infos := Conn.ConnectionInfo; + InfoText := ''; + for i:=0 to Infos.Count-1 do begin + InfoText := InfoText + Infos.Names[i] + ': ' + Infos.ValueFromIndex[i] + sLineBreak; + end; + MessageDialog(Trim(InfoText), mtInformation, [mbOK]); + end; +end;} + + +{procedure TMainForm.actCodeFoldingEndRegionExecute(Sender: TObject); +var + Memo: TSynMemo; +begin + // Insert #endregion + if not actCodeFolding.Checked then + actCodeFolding.Execute; + Memo := ActiveSynMemo(False); + Memo.InsertLine(Memo.CaretXY, Memo.CaretXY, '#endregion ', True); +end;} + + +{procedure TMainForm.actCodeFoldingFoldSelectionExecute(Sender: TObject); +var + Memo: TSynMemo; + AfterText: String; +begin + // Wrap selected text in region/endregion + if not actCodeFolding.Checked then + actCodeFolding.Execute; + Memo := ActiveSynMemo(False); + AfterText := IfThen(Memo.SelText.EndsWith(sLineBreak), '', sLineBreak); + Memo.SelText := '#region ' + sLineBreak + Memo.SelText + AfterText + '#endregion' + sLineBreak; +end;} + + +{procedure TMainForm.PopupQueryLoadPopup(Sender: TObject); +var + i, j: Integer; + Item, SnippetsFolder: TMenuItem; + Filename: String; +begin + // Fill the popupQueryLoad menu + popupQueryLoad.Items.Clear; + + // Apply shared system image list + popupQueryLoad.Images := GetSystemImageList; + + // Snippets + SetSnippetFilenames; + SnippetsFolder := TMenuItem.Create(popupQueryLoad); + SnippetsFolder.Caption := _('Snippets'); + popupQueryLoad.Items.Add(SnippetsFolder); + for i:=0 to FSnippetFilenames.Count-1 do begin + Item := TMenuItem.Create(SnippetsFolder); + Item.Caption := FSnippetFilenames[i]; + Item.OnClick := popupQueryLoadClick; + SnippetsFolder.Add(Item); + end; + + // Separator + Item := TMenuItem.Create(popupQueryLoad); + Item.Caption := '-'; + popupQueryLoad.Items.Add(Item); + + // Recent files + j := 0; + for i:=0 to 19 do begin + Filename := AppSettings.ReadString(asSQLfile, IntToStr(i)); + if Filename = '' then + continue; + Inc(j); + Item := TMenuItem.Create( popupQueryLoad ); + Item.Caption := IntToStr(j) + ' ' + Filename; + Item.OnClick := popupQueryLoadClick; + Item.ImageIndex := GetSystemImageIndex(Filename); + popupQueryLoad.Items.Add(Item); + end; + + // Separator + "Remove absent files" + Item := TMenuItem.Create(popupQueryLoad); + Item.Caption := '-'; + popupQueryLoad.Items.Add(Item); + + Item := TMenuItem.Create(popupQueryLoad); + Item.Caption := _('Remove absent files'); + Item.OnClick := PopupQueryLoadRemoveAbsentFiles; + popupQueryLoad.Items.Add(Item); + + Item := TMenuItem.Create(popupQueryLoad); + Item.Caption := _('Clear file list'); + Item.OnClick := PopupQueryLoadRemoveAllFiles; + popupQueryLoad.Items.Add(Item); +end;} + + +{procedure TMainform.PopupQueryLoadRemoveAbsentFiles(Sender: TObject); +begin + AddOrRemoveFromQueryLoadHistory('', False, True); +end;} + + +{procedure TMainform.PopupQueryLoadRemoveAllFiles(Sender: TObject); +var + i: Integer; +begin + for i:=0 to 20 do begin + if not AppSettings.DeleteValue(asSQLfile, IntToStr(i)) then + break; + end; +end;} + + +{procedure TMainform.popupQueryLoadClick(Sender: TObject); +var + Filename: String; + FileList: TStringList; + p: Integer; + Tab: TQueryTab; +begin + // Click on the popupQueryLoad + Filename := (Sender as TMenuItem).Caption; + Filename := StripHotkey(Filename); + if Pos('\', Filename) = 0 then // assuming we load a snippet + Filename := AppSettings.DirnameSnippets + Filename + '.sql' + else begin // assuming we load a file from the recent-list + p := Pos(' ', Filename) + 1; + filename := Copy(Filename, p, Length(Filename)); + end; + FileList := TStringList.Create; + FileList.Add(Filename); + if not RunQueryFiles(FileList, nil, false) then begin + Tab := GetOrCreateEmptyQueryTab(True); + Tab.LoadContents(Filename, True, nil); + end; + FileList.Free; +end;} + + +{procedure TMainform.AddOrRemoveFromQueryLoadHistory(Filename: String; AddIt: Boolean; CheckIfFileExists: Boolean); +var + i: Integer; + newfilelist: TStringList; + savedfilename: String; +begin + // Add or remove filename to/from history, avoiding duplicates + + newfilelist := TStringList.create; + AppSettings.ResetPath; + + // Add new filename + if AddIt then + newfilelist.Add( filename ); + + // Add all other filenames + for i:=0 to 20 do begin + savedfilename := AppSettings.ReadString(asSQLfile, IntToStr(i)); + if savedfilename.IsEmpty then + Break; + AppSettings.DeleteValue(asSQLfile, IntToStr(i)); + if CheckIfFileExists and (not FileExists( savedfilename )) then + continue; + if (savedfilename <> filename) and (newfilelist.IndexOf(savedfilename)=-1) then + newfilelist.add( savedfilename ); + end; + + // Save new list + for i := 0 to newfilelist.Count-1 do begin + if i >= 20 then + break; + AppSettings.WriteString(asSQLfile, newfilelist[i], IntToStr(i)); + end; +end;} + + +{** + Change default delimiter for SQL execution +} +{procedure TMainForm.actSetDelimiterExecute(Sender: TObject); +var + newVal: String; + ok: Boolean; +begin + // Use a while loop to redisplay the input dialog after setting an invalid value + ok := False; + while not ok do begin + newVal := delimiter; + if InputQuery(_('Set delimiter'), _('SQL statement delimiter (default is ";"):'), newVal) then try + // Set new value + Delimiter := newVal; + ok := True; + except on E:Exception do + ErrorDialog(E.Message); + end else // Cancel clicked + ok := True; + end; +end;} + + +{** + Sets the Delimiter property plus updates the hint on actSetDelimiter +} +procedure TMainForm.SetDelimiter(Value: String); +var + rx: TRegExpr; + Msg: String; +begin + Value := Trim(Value); + Msg := ''; + if Value = '' then + Msg := _('Empty value.') + else begin + rx := TRegExpr.Create; + rx.Expression := '(/\*|--|#|\''|\"|`)'; + if rx.Exec(Value) then + Msg := _('Start-of-comment tokens or string literal markers are not allowed.') + end; + if Msg <> '' then begin + Msg := f_('Error setting delimiter to "%s": %s', [Value, Msg]); + LogSQL(Msg, lcError); + //ErrorDialog(Msg); + end else begin + FDelimiter := Value; + LogSQL(f_('Delimiter changed to %s', [FDelimiter]), lcInfo); + //actSetDelimiter.Hint := actSetDelimiter.Caption + ' (' + _('current value:') + ' ' + FDelimiter + ')'; + end; +end; + + +{procedure TMainForm.actApplyFilterExecute(Sender: TObject); +var + i: Integer; + Filters: TStringList; + val: String; +begin + // If filter box is empty but filter generator box has text, most users expect + // the filter to be auto generated on button click + if ((SynMemoFilter.GetTextLen = 0) or menuAlwaysGenerateFilter.Checked) + and (editFilterSearch.Text <> '') + and (Sender is TAction) + and ((Sender as TAction).ActionComponent = btnFilterApply) + then begin + editFilterSearchChange(editFilterSearch); + end; + + if SynMemoFilter.GetTextLen > 0 then begin + // Recreate recent filters list + Filters := TStringList.Create; + Filters.Add(Trim(SynMemoFilter.Text)); + AppSettings.SessionPath := GetRegKeyTable+'\'+REGKEY_RECENTFILTERS; + // Add old filters + for i:=1 to 20 do begin + val := AppSettings.ReadString(asRecentFilter, IntToStr(i)); + if val.IsEmpty then + Continue; + if Filters.IndexOf(val) = -1 then + Filters.Add(val); + AppSettings.DeleteValue(asRecentFilter, IntToStr(i)); + end; + for i:=0 to Filters.Count-1 do + AppSettings.WriteString(asRecentFilter, Filters[i], IntToStr(i+1)); + FreeAndNil(Filters); + AppSettings.ResetPath; + end; + // Keep current column widths on "Quick filter" clicks, don't keep them on "Apply filter" clicks + if (Sender is TMenuItem) and ((Sender as TMenuItem).GetParentMenu = popupDataGrid) then begin + FDataGridColumnWidthsCustomized := True; + end else + FDataGridColumnWidthsCustomized := False; + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); +end;} + + +{procedure TMainForm.actDataFirstExecute(Sender: TObject); +var + Node: PVirtualNode; +begin + Node := GetNextNode(ActiveGrid, nil); + if Assigned(Node) then + SelectNode(ActiveGrid, Node); + ValidateControls(Sender); +end;} + + +{procedure TMainForm.actDataInsertExecute(Sender: TObject); +var + DupeNode, NewNode: PVirtualNode; + Grid: TVirtualStringTree; + Results: TDBQuery; + RowNum: Int64; + DupeNum: PInt64; + Col, ResultCol: Integer; + Value: String; + IsNull, AllowNewNode: Boolean; +begin + Grid := ActiveGrid; + Results := GridResult(Grid); + // Pre-test if changing node focus is allowed, in cases where current row modifications throw some SQL error when posting + AllowNewNode := False; + Grid.OnFocusChanging(Grid, Grid.FocusedNode, nil, Grid.FocusedColumn, Grid.FocusedColumn, AllowNewNode); + if not AllowNewNode then + exit; + try + Results.CheckEditable; + DupeNode := nil; + if (Sender = actDataDuplicateRowWithoutKeys) or (Sender = actDataDuplicateRowWithKeys) then + DupeNode := Grid.FocusedNode; + RowNum := Results.InsertRow; + NewNode := Grid.InsertNode(Grid.FocusedNode, amInsertAfter, PInt64(RowNum)); + SelectNode(Grid, NewNode); + if Assigned(DupeNode) then begin + // Copy values from source row, ensure we have whole cell data + DupeNum := Grid.GetNodeData(DupeNode); + AnyGridEnsureFullRow(Grid, DupeNode); + for Col:=0 to Grid.Header.Columns.Count-1 do begin + ResultCol := Col - 1; + if not (coVisible in Grid.Header.Columns[Col].Options) then + continue; // Ignore invisible key column + if ResultCol < 0 then + Continue; // Ignore static row id column + if Results.ColIsPrimaryKeyPart(ResultCol) and (Sender = actDataDuplicateRowWithoutKeys) then + continue; // Empty value for primary key column + if Results.ColIsVirtual(ResultCol) then + continue; // Don't copy virtual column value + Results.RecNo := DupeNum^; + Value := Results.Col(ResultCol); + IsNull := Results.IsNull(ResultCol); + Results.RecNo := RowNum; + Results.SetCol(ResultCol, Value, IsNull, False); + end; + end; + except on E:EDbError do + ErrorDialog(_('Grid editing error'), E.Message); + end; +end;} + + +{procedure TMainForm.actDataLastExecute(Sender: TObject); +var + Node: PVirtualNode; + Grid: TVirtualStringTree; +begin + Grid := ActiveGrid; + // Be sure to have all rows + if (Grid = DataGrid) and (DatagridWantedRowCount < AppSettings.ReadInt(asDatagridMaximumRows)) then + actDataShowAll.Execute; + Node := Grid.GetLast; + if Assigned(Node) then + SelectNode(Grid, Node); + ValidateControls(Sender); +end;} + + +{procedure TMainForm.actDataOpenUrlExecute(Sender: TObject); +var + Grid: TVirtualStringTree; +begin + // Open grid cell url in web browser + Grid := ActiveGrid; + ShellExec(Grid.Text[Grid.FocusedNode, Grid.FocusedColumn]); +end;} + + +{procedure TMainForm.actDataPostChangesExecute(Sender: TObject); +var + Grid: TVirtualStringTree; + Results: TDBQuery; +begin + if Sender is TVirtualStringTree then + Grid := Sender as TVirtualStringTree + else + Grid := ActiveGrid; + Results := GridResult(Grid); + Results.SaveModifications; + // Node needs a repaint to remove red triangles + if Assigned(Grid.FocusedNode) then + Grid.InvalidateNode(Grid.FocusedNode); + DisplayRowCountStats(Grid); +end;} + +{procedure TMainForm.actRemoveFilterExecute(Sender: TObject); +begin + actClearFilterEditor.Execute; + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); +end;} + + +{procedure TMainForm.actDataCancelChangesExecute(Sender: TObject); +var + Grid: TVirtualStringTree; + Results: TDBQuery; + RowNum: PInt64; + Node, FocNode: PVirtualNode; +begin + // Cancel INSERT or UPDATE mode + Grid := ActiveGrid; + Node := Grid.FocusedNode; + if Assigned(Node) then begin + Results := GridResult(Grid); + RowNum := Grid.GetNodeData(Node); + Results.RecNo := RowNum^; + Results.DiscardModifications; + if Results.Inserted then begin + FocNode := Grid.GetPreviousSibling(Node); + Grid.DeleteNode(Node); + SelectNode(Grid, FocNode); + end else + Grid.InvalidateNode(Node); + ValidateControls(Sender); + end; +end;} + + +{** + Add a SQL-command or comment to SynMemoSQLLog +} +procedure TMainForm.LogSQL(Msg: String; Category: TDBLogCategory=lcInfo; Connection: TDBConnection=nil); +var + snip, IsSQL: Boolean; + Len, i, MaxLineWidth: Integer; + Sess, OldSettingsPath: String; + LogIt: Boolean; + LogItem: TDBLogItem; +begin + //OldSettingsPath := AppSettings.SessionPath; + LogItem := TDBLogItem.Create; + LogItem.Category := Category; + if false then // AppSettings.ReadBool(asLogTimestamp) then + LogItem.LineText := '['+FormatDateTime('hh:nn:ss.zzz', Now)+'] '+ Msg + else + LogItem.LineText := Msg; + LogItem.Connection := Connection; + PostponedLogItems.Add(LogItem); + + if not MainFormCreated then + Exit; + if csDestroying in ComponentState then + Exit; + + for LogItem in PostponedLogItems do begin + + // Log only wanted events + {case LogItem.Category of + lcError: LogIt := AppSettings.ReadBool(asLogErrors); + lcUserFiredSQL: LogIt := AppSettings.ReadBool(asLogUserSQL); + lcSQL: LogIt := AppSettings.ReadBool(asLogSQL); + lcScript: LogIt := AppSettings.ReadBool(asLogScript); + lcInfo: LogIt := AppSettings.ReadBool(asLogInfos); + lcDebug: LogIt := AppSettings.ReadBool(asLogDebug); + else LogIt := False; + end;} + LogIt := True; + + if LogIt then begin + // Shorten very long messages + Msg := LogItem.LineText; + Len := Length(Msg); + MaxLineWidth := 0; // AppSettings.ReadInt(asLogsqlwidth); + snip := (MaxLineWidth > 0) and (Len > MaxLineWidth); + IsSQL := LogItem.Category in [lcSQL, lcUserFiredSQL]; + if snip then begin + Msg := + Copy(Msg, 0, MaxLineWidth) + + '/* '+f_('large SQL query (%s), snipped at %s characters', [Len.ToString, MaxLineWidth.ToString]) + ' */'; + end else if (not snip) and IsSQL then + Msg := Msg + Delimiter; + if not IsSQL then + Msg := '/* ' + Msg + ' */'; + + SynMemoSQLLog.Lines.Add(Msg); + + // Delete first line(s) in SQL log and adjust LineNumberStart in gutter + {i := 0; + while SynMemoSQLLog.Lines.Count > AppSettings.ReadInt(asLogsqlnum) do begin + SynMemoSQLLog.Lines.Delete(0); + Inc(i); + end; + // Increase first displayed number in gutter so it doesn't lie about the log entries + if i > 0 then + SynMemoSQLLog.Gutter.LineNumberStart := SynMemoSQLLog.Gutter.LineNumberStart + i;} + + // Scroll to last line and repaint + SynMemoSQLLog.CaretY := SynMemoSQLLog.Lines.Count; + // Causes access violations on a reconnected session firing a user-query: + // SynMemoSQLLog.Repaint; + // SynMemoSQLLog.Update; + // See TDBConnection.Log and TQueryThread.LogFromThread + // See https://github.com/HeidiSQL/HeidiSQL/issues/57 + + // Log to file? + if FLogToFile then + try + Sess := ''; + //if LogItem.Connection <> nil then + // Sess := LogItem.Connection.Parameters.SessionPath; + WriteLn(FFileHandleSessionLog, Format('/* %s [%s] */ %s', [DateTimeToStr(Now), Sess, msg])); + except + on E:Exception do begin + LogToFile := False; + //AppSettings.WriteBool(asLogToFile, False); + //ErrorDialog(_('Error writing to session log file.'), E.Message+CRLF+_('Filename')+': '+FFileNameSessionLog+CRLF+CRLF+_('Logging is disabled now.')); + end; + end; + end; + end; + PostponedLogItems.Clear; + + // Restore possibly overwritten session path + //AppSettings.SessionPath := OldSettingsPath; +end; + + +{procedure TMainForm.actDataShowNextExecute(Sender: TObject); +var + OldRowCount: Int64; +begin + // Show next X rows in datagrid + OldRowCount := DatagridWantedRowCount; + Inc(DatagridWantedRowCount, AppSettings.ReadInt(asDatagridRowsPerStep)); + DataGridWantedRowCount := Min(DataGridWantedRowCount, AppSettings.ReadInt(asDatagridMaximumRows)); + InvalidateVT(DataGrid, VTREE_NOTLOADED, True); + SelectNode(DataGrid, OldRowCount); +end;} + + +{procedure TMainForm.actAttachDatabaseExecute(Sender: TObject); +var + Selector: TOpenDialog; + OldFiles, NewFiles: TStringList; + i: Integer; + Conn: TDBConnection; + DbAlias: String; +begin + // Attach new or existing SQLite database file + Selector := TOpenDialog.Create(Self); + //Selector.InitialDir := ?; + Selector.Filter := 'SQLite databases ('+FILEFILTER_SQLITEDB+')|'+FILEFILTER_SQLITEDB+'|'+_('All files')+' (*.*)|*.*'; + Selector.Options := Selector.Options - [ofFileMustExist]; + Selector.Options := Selector.Options + [ofAllowMultiSelect]; + Selector.DefaultExt := FILEEXT_SQLITEDB; + if Selector.Execute then begin + Conn := ActiveConnection; + OldFiles := Explode(DELIM, Conn.Parameters.Hostname); + NewFiles := TStringList.Create; + NewFiles.Assign(Selector.Files); + try + for i:=0 to NewFiles.Count-1 do begin + // Remove path if it's the application directory + if ExtractFilePath(NewFiles[i]) = ExtractFilePath(Application.ExeName) then + NewFiles[i] := ExtractFileName(NewFiles[i]); + if OldFiles.IndexOf(NewFiles[i]) = -1 then begin + OldFiles.Add(NewFiles[i]); + DbAlias := TPath.GetFileNameWithoutExtension(NewFiles[i]); + Conn.Query('ATTACH DATABASE '+Conn.EscapeString(NewFiles[i])+' AS '+Conn.QuoteIdent(DbAlias)); + end; + end; + AppSettings.SessionPath := Conn.Parameters.SessionPath; + AppSettings.WriteString(asHost, Implode(DELIM, OldFiles)); + RefreshTree; + except + on E:EDbError do begin + ErrorDialog(E.Message); + end; + end; + end; +end;} + +{procedure TMainForm.actDetachDatabaseExecute(Sender: TObject); +var + Obj: TDBObject; + OldFiles: TStringList; + i: Integer; + DbAlias: String; +begin + // Detach previously attached SQLite database file + Obj := ActiveDBObj; + if Obj.NodeType <> lntDb then + Exit; + if MessageDialog( + f_('Detach database "%s" from "%s" session?', [Obj.Database, Obj.Connection.Parameters.SessionPath]) + + CRLF + CRLF + _('Note: The database file will not get deleted.'), + mtConfirmation, + [mbYes, mbNo]) <> mrYes then + Exit; + try + Obj.Connection.Query('DETACH DATABASE '+Obj.Connection.QuoteIdent(Obj.Database)); + OldFiles := Explode(DELIM, Obj.Connection.Parameters.Hostname); + for i:=0 to OldFiles.Count-1 do begin + DbAlias := TPath.GetFileNameWithoutExtension(OldFiles[i]); + if DbAlias = Obj.Database then begin + OldFiles.Delete(i); + Break; + end; + end; + AppSettings.SessionPath := Obj.Connection.Parameters.SessionPath; + AppSettings.WriteString(asHost, Implode(DELIM, OldFiles)); + RefreshTree; + except + on E:EDbError do begin + ErrorDialog(E.Message); + end; + end; +end;} + + +{procedure TMainForm.actDataShowAllExecute(Sender: TObject); +begin + // Remove LIMIT clause + DatagridWantedRowCount := AppSettings.ReadInt(asDatagridMaximumRows); + InvalidateVT(DataGrid, VTREE_NOTLOADED, True); +end;} + + +{function TMainForm.AnyGridEnsureFullRow(Grid: TVirtualStringTree; Node: PVirtualNode): Boolean; +var + RowNum: PInt64; + Data: TDBQuery; +begin + // Load remaining data on a partially loaded row in data grid + Result := True; + if (Grid = DataGrid) and Assigned(Node) then begin + RowNum := Grid.GetNodeData(Node); + Data := GridResult(Grid); + Data.RecNo := RowNum^; + Result := Data.EnsureFullRow(False); + end; +end;} + + +{procedure TMainForm.DataGridEnsureFullRows(Grid: TVirtualStringTree; SelectedOnly: Boolean); +var + Node: PVirtualNode; + Results: TDBQuery; + RowNum: PInt64; +begin + // Load remaining data of all grid rows + Results := GridResult(Grid); + Node := GetNextNode(Grid, nil, SelectedOnly); + while Assigned(Node) do begin + RowNum := Grid.GetNodeData(Node); + Results.RecNo := RowNum^; + if not Results.HasFullData then begin + DataGridFullRowMode := True; + InvalidateVT(Grid, VTREE_NOTLOADED_PURGECACHE, True); + break; + end; + Node := GetNextNode(Grid, Node, SelectedOnly); + end; +end;} + + +{procedure TMainForm.AnyGridHeaderDrawQueryElements(Sender: TVTHeader; + var PaintInfo: THeaderPaintInfo; var Elements: THeaderPaintElements); +begin + // Tell the tree we want to paint most of the column header things ourselves + // Only called when Header.OwnerDraw is True + Elements := [hpeHeaderGlyph, hpeText, hpeOverlay]; +end;} + + +{procedure TMainForm.AnyGridAdvancedHeaderDraw(Sender: TVTHeader; + var PaintInfo: THeaderPaintInfo; const Elements: THeaderPaintElements); +var + PaintArea, TextArea, IconArea, SortArea: TRect; + SortText, ColCaption: String; + TextSpace, ColSortIndex, NumCharTop: Integer; + ColSortDirection: VirtualTrees.TSortDirection; + Size: TSize; + DC: HDC; + DrawFormat: Cardinal; +const + NumSortChars: Array of Char = ['¹','²','³','â´','âµ','â¶','â·','â¸','â¹','âº']; + + procedure GetSortIndex(Column: TVirtualTreeColumn; var SortIndex: Integer; var SortDirection: VirtualTrees.TSortDirection); + var + SortItem: TSortItem; + begin + SortIndex := -1; + if Column.Owner.Header.Treeview = DataGrid then begin + // Data grid supports multiple sorted columns + SortItem := FDataGridSortItems.FindByColumn(PaintInfo.Column.Text); + if Assigned(SortItem) then begin + SortIndex := FDataGridSortItems.IndexOf(SortItem); + if SortItem.Order = sioAscending then + SortDirection := sdAscending + else + SortDirection := sdDescending; + end; + + end else begin + // We're in a query grid, supporting a single sorted column + if Column.Owner.Header.SortColumn = Column.Index then begin + SortIndex := 0; + SortDirection := Column.Owner.Header.SortDirection; + end; + end; + end; + +begin + // Paint specified elements on column header + + PaintArea := PaintInfo.PaintRectangle; + PaintArea.Inflate(-PaintInfo.Column.Margin, 0); + DC := PaintInfo.TargetCanvas.Handle; + + // Draw column name. Code taken from TVirtualTreeColumns.DrawButtonText and modified for our needs + if hpeText in Elements then begin + ColCaption := PaintInfo.Column.Text; + // Leave space for icons + TextArea := PaintArea; + if PaintInfo.Column.ImageIndex > -1 then + Dec(TextArea.Right, Sender.Images.Width); + GetSortIndex(PaintInfo.Column, ColSortIndex, ColSortDirection); + if ColSortIndex > -1 then + Dec(TextArea.Right, Sender.Images.Width); + + if not (coWrapCaption in PaintInfo.Column.Options) then begin + // Do we need to shorten the caption due to limited space? + GetTextExtentPoint32W(DC, PWideChar(ColCaption), Length(ColCaption), Size); + TextSpace := TextArea.Right - TextArea.Left; + if TextSpace < Size.cx then + ColCaption := VirtualTrees.Utils.ShortenString(DC, ColCaption, TextSpace); + end; + + SetBkMode(DC, TRANSPARENT); + SetTextColor(DC, ColorToRGB(clWindowText)); + DrawFormat := DT_TOP or DT_NOPREFIX or DT_LEFT; + DrawTextW(DC, PWideChar(ColCaption), Length(ColCaption), TextArea, DrawFormat); + end; + + // Draw image, if any + if (hpeHeaderGlyph in Elements) and (PaintInfo.Column.ImageIndex > -1) then begin + IconArea := PaintArea; + Inc(IconArea.Left, IconArea.Width - Sender.Images.Width); + GetSortIndex(PaintInfo.Column, ColSortIndex, ColSortDirection); + if ColSortIndex > -1 then + Dec(IconArea.Left, Sender.Images.Width); + Sender.Images.Draw(PaintInfo.TargetCanvas, IconArea.Left, IconArea.Top, PaintInfo.Column.ImageIndex); + end; + + // Paint sort icon and number + if hpeOverlay in Elements then begin + SortArea := PaintArea; + GetSortIndex(PaintInfo.Column, ColSortIndex, ColSortDirection); + if ColSortIndex > -1 then begin + Inc(SortArea.Left, SortArea.Width - Sender.Images.Width); + // Prepare default font size, also if user selected a bigger one for the grid - we reserved a 16x16 space. + // Font.Height + Font.Size must be set with these values to get this working, larger or smaller Size/Height + // result in wrong size for multiple sort columns. + PaintInfo.TargetCanvas.Font.Height := -11; + PaintInfo.TargetCanvas.Font.Size := 10; + if ColSortDirection = sdAscending then begin + // This is a bit wrong - but the "Ubuntu" font doesn't have the triangle character, + // which seems available on many Windows fonts only. See #1090 + SortText := IfThen(IsWine, '↑', 'â–²'); + NumCharTop := 0; + end else begin + SortText := IfThen(IsWine, '↓', 'â–¼'); + NumCharTop := 5; + end; + // Paint arrow: + PaintInfo.TargetCanvas.TextOut(SortArea.Left, SortArea.Top, SortText); + // ... and superscript number right besides: + SortText := IfThen(ColSortIndex<9, NumSortChars[ColSortIndex], NumSortChars[9]); + PaintInfo.TargetCanvas.TextOut(SortArea.Left+9, SortArea.Top+NumCharTop, SortText); + end; + end; +end;} + + +{procedure TMainForm.DataGridBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); +var + vt: TVirtualStringTree; + Select, FixedFilter: String; + RefreshingData, IsKeyColumn, NeedFullColumns: Boolean; + i, ColWidth, VisibleColumns, MaximumRows, FullColumnCount: Integer; + ColMaxLen, Offset: Int64; + ColWidths, WantedColumnOrgnames: TStringList; + KeyCols, WantedColumns: TTableColumnList; + c, ColumnInKey: TTableColumn; + OldScrollOffset: TPoint; + DBObj: TDBObject; + rx: TRegExpr; + OldCursor: TBufferCoord; + Col: TVirtualTreeColumn; + + procedure InitColumn(idx: Integer; TblCol: TTableColumn); + var + k: Integer; + begin + col := vt.Header.Columns.Add; + col.Text := TblCol.Name; + col.Hint := TblCol.Comment; + col.Options := col.Options + [coSmartResize]; + if DatagridHiddenColumns.IndexOf(TblCol.Name) > -1 then + col.Options := col.Options - [coVisible]; + // Column header icon + for k:=0 to SelectedTableKeys.Count-1 do begin + if SelectedTableKeys[k].Columns.IndexOf(TblCol.Name) > -1 then begin + col.ImageIndex := SelectedTableKeys[k].ImageIndex; + break; + end; + end; + if col.ImageIndex = -1 then begin + if SelectedTableTimestampColumns.IndexOf(TblCol.Name) > -1 then + col.ImageIndex := 149; + end; + + // Text alignment in grid cells + col.Alignment := taLeftJustify; + if DataGridResult.DataType(idx).Category in [dtcInteger, dtcReal] then + col.Alignment := taRightJustify; + end; + +begin + // Load data into data tab grid + vt := Sender as TVirtualStringTree; + if vt.Tag = VTREE_LOADED then + Exit; + DBObj := ActiveDbObj; + if DBObj = nil then + Exit; + Screen.Cursor := crHourglass; + DBObj.Connection.Ping(True); + + if SelectedTableColumns.Count = 0 then begin + EnableDataTab(False); + end else begin + EnableDataTab(True); + + // Indicates whether the current table data is just refreshed or if we're in another table + // ... or maybe in a table/database with the same name on a different server + RefreshingData := DBObj.IsSameAs(DataGridTable); + + // Load last view settings + HandleDataGridAttributes(RefreshingData); + OldScrollOffset := DataGrid.OffsetXY; + + // Remember old column widths if customized + ColWidths := TStringList.Create; + if not RefreshingData then + FDataGridColumnWidthsCustomized := False; + if FDataGridColumnWidthsCustomized then begin + for i:=0 to vt.Header.Columns.Count-1 do + ColWidths.Values[vt.Header.Columns[i].Text] := IntToStr(vt.Header.Columns[i].Width); + end; + + DataGridTable := DBObj; + + Select := ''; + // Ensure key columns are included to enable editing + KeyCols := DBObj.Connection.GetKeyColumns(SelectedTableColumns, SelectedTableKeys); + WantedColumns := TTableColumnList.Create(False); + WantedColumnOrgnames := TStringList.Create; + FullColumnCount := 0; + // If any column has INVISIBLE attribute: + NeedFullColumns := False; + for i:=0 to SelectedTableColumns.Count-1 do begin + c := SelectedTableColumns[i]; + ColumnInKey := KeyCols.FindByName(c.Name); + IsKeyColumn := Assigned(ColumnInKey); + ColMaxLen := StrToInt64Def(c.LengthSet, 0); + if (DatagridHiddenColumns.IndexOf(c.Name) = -1) + or (IsKeyColumn) + or (KeyCols.Count = 0) + then begin + if not DataGridFullRowMode + and (KeyCols.Count > 0) // We need a sufficient key to be able to load remaining row data + and (c.DataType.LoadPart) + and (not IsKeyColumn) // We need full length of any key column, so DataGridLoadFullRow() has the chance to fetch the right row + and ((ColMaxLen > GRIDMAXDATA) or (ColMaxLen = 0)) // No need to blow SQL with LEFT() if column is shorter anyway + then begin + Select := Select + DBObj.Connection.GetSQLSpecifity(spFuncLeft, [c.CastAsText, GRIDMAXDATA]) + ', '; + end else if DBObj.Connection.Parameters.IsAnyMSSQL and (c.DataType.Index=dbdtTimestamp) then begin + Select := Select + ' CAST(' + DBObj.Connection.QuoteIdent(c.Name) + ' AS INT), '; + end else if DBObj.Connection.Parameters.IsAnyMSSQL and (c.DataType.Index=dbdtHierarchyid) then begin + Select := Select + ' CAST(' + DBObj.Connection.QuoteIdent(c.Name) + ' AS NVARCHAR('+IntToStr(GRIDMAXDATA)+')), '; + end else begin + Select := Select + ' ' + DBObj.Connection.QuoteIdent(c.Name) + ', '; + Inc(FullColumnCount); + end; + WantedColumns.Add(c); + WantedColumnOrgnames.Add(c.Name); + NeedFullColumns := NeedFullColumns or c.Invisible; + end; + end; + // Cut last comma + Delete(Select, Length(Select)-1, 2); + + // Shorten the whole query if all columns are involved + if (FullColumnCount = SelectedTableColumns.Count) and (not NeedFullColumns) then + Select := '*'; + + // Include db name for cases in which dbtree is switching databases and pending updates are in process + Select := Select + ' FROM '+DBObj.QuotedDbAndTableName; + + // Append WHERE clause, and gracefully allow superfluous WHERE from user input + // Also, don't add a "WHERE ..." when the filter contains comments only + if Length(Trim(TSQLBatch.GetSQLWithoutComments(SynMemoFilter.Text))) > 0 then begin + rx := TRegExpr.Create; + rx.ModifierI := True; + rx.Expression := '^\s*WHERE\s+'; + FixedFilter := rx.Replace(SynMemoFilter.Text, ''); + if FixedFilter <> SynMemoFilter.Text then begin + OldCursor := SynMemoFilter.CaretXY; + SynMemoFilter.Text := FixedFilter; + SynMemoFilter.CaretXY := OldCursor; + end; + rx.Free; + Select := Select + ' WHERE ' + SynMemoFilter.Text + CRLF; + tbtnDataFilter.ImageIndex := 108; + end else + tbtnDataFilter.ImageIndex := 107; + SynMemoFilter.OnStatusChange(SynMemoFilter, []); + + // Append ORDER clause + if FDataGridSortItems.Count > 0 then begin + Select := Select + ' ORDER BY ' + FDataGridSortItems.ComposeOrderClause(DBObj.Connection); + tbtnDataSorting.ImageIndex := 108; + tbtnDataSorting.Caption := _('Sorting') + ' ('+IntToStr(FDataGridSortItems.Count)+')'; + end else begin + tbtnDataSorting.ImageIndex := 107; + tbtnDataSorting.Caption := _('Sorting'); + end; + + // Append LIMIT clause + Offset := 0; + if RefreshingData and (vt.Tag <> VTREE_NOTLOADED_PURGECACHE) then begin + case DBObj.Connection.Parameters.NetTypeGroup of + ngMSSQL: Offset := 0; // Does not support offset in all server versions + ngMySQL, ngPgSQL, ngSQLite: Offset := DataGridResult.RecordCount; + else raise Exception.CreateFmt(_(MsgUnhandledNetType), [Integer(DBObj.Connection.Parameters.NetType)]); + end; + end; + Select := DBObj.Connection.ApplyLimitClause('SELECT', Select, DatagridWantedRowCount-Offset, Offset); + + vt.BeginUpdate; + vt.Header.Columns.Clear; + vt.Clear; + + try + ShowStatusMsg(_('Fetching rows ...')); + // Result object must be of the right vendor type + if not RefreshingData then begin + FreeAndNil(DataGridResult); + DataGridResult := DBObj.Connection.Parameters.CreateQuery(DBObj.Connection); + end; + DataGridResult.DBObject := DBObj; + DataGridResult.SQL := Trim(Select); + DataGridResult.Execute(Offset > 0); + DataGridResult.ColumnOrgNames := WantedColumnOrgnames; + try + DataGridResult.PrepareEditing; + except on E:EDbError do // Do not annoy user with popup when accessing tables in information_schema + LogSQL(_('Data in this table will be read-only.')); + end; + + editFilterVT.Clear; + TimerFilterVT.OnTimer(Sender); + + // Assign new data + vt.RootNodeCount := DataGridResult.RecordCount; + + // Set up grid column headers + ShowStatusMsg(_('Setting up columns ...')); + VisibleColumns := 0; + Col := vt.Header.Columns.Add; + Col.CaptionAlignment := taRightJustify; + Col.Alignment := taRightJustify; + Col.Options := col.Options + [coFixed]- [coAllowClick, coAllowFocus, coEditable, coResizable]; + if not AppSettings.ReadBool(asShowRowId) then + Col.Options := col.Options - [coVisible]; + Col.Text := '#'; + for i:=0 to WantedColumns.Count-1 do begin + InitColumn(i, WantedColumns[i]); + if coVisible in vt.Header.Columns[i+1].Options then + Inc(VisibleColumns); + end; + + // Signal for the user if we hide some columns + if VisibleColumns = SelectedTableColumns.Count then + tbtnDataColumns.ImageIndex := 107 + else + tbtnDataColumns.ImageIndex := 108; + tbtnDataColumns.Caption := _('Columns') + ' ('+IntToStr(VisibleColumns)+'/'+IntToStr(SelectedTableColumns.Count)+')'; + + // Autoset or restore column width + for i:=0 to vt.Header.Columns.Count-1 do begin + ColWidth := 0; + if RefreshingData then + ColWidth := StrToIntDef(ColWidths.Values[vt.Header.Columns[i].Text], ColWidth); + if ColWidth > 0 then + vt.Header.Columns[i].Width := ColWidth + else + AutoCalcColWidth(vt, i); + end; + + except + // Wrong WHERE clause in most cases + on E:EDbError do + ErrorDialog(E.Message); + end; + + vt.EndUpdate; + ApplyFontToGrids; + + // Do not steel filter while writing filters + if not SynMemoFilter.Focused then + vt.TrySetFocus; + + DataGridFocusedNodeIndex := Min(DataGridFocusedNodeIndex, Int64(vt.RootNodeCount)-1); + SelectNode(vt, DataGridFocusedNodeIndex); + for i:=0 to vt.Header.Columns.Count-1 do begin + if vt.Header.Columns[i].Text = DataGridFocusedColumnName then begin + vt.FocusedColumn := i; + break; + end; + end; + if RefreshingData then begin + + if (FDataGridLastClickedColumnHeader >= 0) and (FDataGridLastClickedColumnHeader < vt.Header.Columns.Count) then begin // See issue #3309 + // Horizontal offset based on the left side of a just sorted column + OldScrollOffset.X := -(vt.Header.Columns[FDataGridLastClickedColumnHeader].Left - vt.OffsetX - FDataGridLastClickedColumnLeftPos); + // logsql('Fixing x-offset to '+OldScrollOffset.X.ToString + + // ', FDataGridLastClickedColumnHeader:'+FDataGridLastClickedColumnHeader.ToString + + // ', FDataGridLastClickedColumnLeftPos: '+FDataGridLastClickedColumnLeftPos.ToString + + // ', vt.Header.Columns[FDataGridLastClickedColumnHeader].Left: '+vt.Header.Columns[FDataGridLastClickedColumnHeader].Left.ToString + // ); + end; + + vt.OffsetXY := OldScrollOffset; + end; + + // Reset remembered data for last clicked column header + FDataGridLastClickedColumnHeader := NoColumn; + FDataGridLastClickedColumnLeftPos := -1; + + vt.Header.Invalidate(nil); + vt.UpdateScrollBars(True); + ValidateControls(Sender); + DisplayRowCountStats(vt); + MaximumRows := AppSettings.ReadInt(asDatagridMaximumRows); + actDataShowNext.Enabled := (vt.RootNodeCount = DatagridWantedRowCount) and (DatagridWantedRowCount < MaximumRows); + actDataShowAll.Enabled := actDataShowNext.Enabled; + EnumerateRecentFilters; + ColWidths.Free; + if Integer(vt.RootNodeCount) = MaximumRows then + LogSQL(f_('Browsing is currently limited to a maximum of %s rows. To see more rows, increase this maximum in %s > %s > %s.', [FormatNumber(MaximumRows), _('Tools'), _('Preferences'), _('Data')]), lcInfo); + end; + vt.Tag := VTREE_LOADED; + DataGridFullRowMode := False; + Screen.Cursor := crDefault; + ShowStatusMsg; +end;} + + +{procedure TMainForm.DataGridColumnResize(Sender: TVTHeader; Column: TColumnIndex); +begin + // Remember current table after last column resizing so we can auto size them as long as this did not happen + if not TBaseVirtualTree(Sender.Treeview).IsUpdating then + FDataGridColumnWidthsCustomized := True; +end;} + + +{*** + Calculate + display total rowcount and found rows matching to filter + in data-tab +} +{procedure TMainForm.DisplayRowCountStats(Sender: TBaseVirtualTree); +var + DBObject: TDBObject; + ObjInCache: PDBObject; + IsFiltered, IsLimited: Boolean; + cap: String; + RowsTotal: Int64; +begin + if Sender <> DataGrid then + Exit; // Only data tab has a top label + + DBObject := ActiveDbObj; + if DBObject = nil then // Some cases have no object, don't let them crash + Exit; + + cap := ActiveDatabase + '.' + DBObject.Name; + IsLimited := DataGridWantedRowCount <= Datagrid.RootNodeCount; + IsFiltered := SynMemoFilter.GetTextLen > 0; + if DBObject.NodeType = lntTable then begin + if (not IsLimited) and (not IsFiltered) then begin + RowsTotal := DataGrid.RootNodeCount; // No need to fetch via SHOW TABLE STATUS + DBObject.RowsAreExact := True; + menuQueryExactRowCount.Enabled := False; + end + else begin + Screen.Cursor := crHourGlass; + if (not DBObject.RowsAreExact) or FExactRowCountMode then + RowsTotal := DBObject.RowCount(True, FExactRowCountMode) + else + RowsTotal := DBObject.Rows; + Screen.Cursor := crDefault; + menuQueryExactRowCount.Enabled := True; + end; + if RowsTotal > -1 then begin + cap := cap + ': ' + FormatNumber(RowsTotal) + ' ' + _('rows total'); + if DBObject.Engine = 'InnoDB' then begin + if DBObject.RowsAreExact then + cap := cap + ' ('+_('exact')+')' + else + cap := cap + ' ('+_('approximately')+')'; + end; + // Display either LIMIT or WHERE effect, not both at the same time + if IsLimited then + cap := cap + ', '+_('limited to') + ' ' + FormatNumber(Datagrid.RootNodeCount) + else if IsFiltered then begin + if Datagrid.RootNodeCount = RowsTotal then + cap := cap + ', '+_('all rows match to filter') + else + cap := cap + ', ' + FormatNumber(Datagrid.RootNodeCount) + ' '+_('rows match to filter'); + end; + // Update cached object reference with new row count, which may enable "Data" option + // in table copy dialog. See issue #666 + if Assigned(DBtree.FocusedNode) then begin + ObjInCache := DBtree.GetNodeData(DBtree.FocusedNode); + if Assigned(ObjInCache) and ObjInCache.IsSameAs(DBObject) then begin + ObjInCache.Rows := RowsTotal; + ObjInCache.RowsAreExact := DBObject.RowsAreExact; + end; + end; + end; + end; + lblDataTop.Caption := cap; + lblDataTop.Hint := cap; + FExactRowCountMode := False; +end;} + + +{procedure TMainForm.menuQueryExactRowCountClick(Sender: TObject); +begin + // Activate exact row count mode and let DisplayRowCountStats do the rest + // See https://www.heidisql.com/forum.php?t=41310 + FExactRowCountMode := True; + DisplayRowCountStats(DataGrid); +end;} + + +{procedure TMainForm.AnyGridInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; + var InitialStates: TVirtualNodeInitStates); +var + Idx: PInt64; +begin + // Display multiline grid rows + // Mark all nodes as multiline capable. Fixes painting issues with long lines. (?) + // See issue #1897 and https://www.heidisql.com/forum.php?t=41502 + // Laggy performance with large grid contents (?) + if AppSettings.ReadInt(asGridRowLineCount) = 1 then + Exclude(Node.States, vsMultiLine) + else + Include(Node.States, vsMultiLine); + Sender.NodeHeight[Node] := TVirtualStringTree(Sender).DefaultNodeHeight; + // Node may have data already, if added via InsertRow + if not (vsOnFreeNodeCallRequired in Node.States) then begin + Idx := Sender.GetNodeData(Node); + Idx^ := Node.Index; + end; +end;} + + +{procedure TMainForm.AnyGridGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); +begin + NodeDataSize := SizeOf(Int64); +end;} + + +{*** + Occurs when active tab has changed. +} +{procedure TMainForm.PageControlMainChange(Sender: TObject); +var + tab: TTabSheet; +begin + // Protect from crash when pressing ctrl+tab before main form is displayed + // See #574 + if not Self.Visible then + Exit; + + tab := PageControlMain.ActivePage; + // Query helpers need a hit here, since RefreshHelperNode now only does its update on the active tab + // See https://www.heidisql.com/forum.php?t=37961 + RefreshHelperNode(TQueryTab.HelperNodeColumns); + RefreshHelperNode(TQueryTab.HelperNodeSnippets); + RefreshHelperNode(TQueryTab.HelperNodeHistory); + + // Move focus to relevant controls in order for them to receive keyboard events. + // Do this only if the user clicked the new tab. Not on automatic tab changes. + if Sender = PageControlMain then begin + if tab = tabHost then + PageControlHostChange(Sender) + else if tab = tabDatabase then + ListTables.TrySetFocus + else if tab = tabData then begin + DataGrid.TrySetFocus; + end else if IsQueryTab(tab.PageIndex, True) then begin + QueryTabs.ActiveMemo.TrySetFocus; + QueryTabs.ActiveMemo.WordWrap := actQueryWordWrap.Checked; + SynMemoQueryStatusChange(QueryTabs.ActiveMemo, [scCaretX]); + end; + end; + + // Filter panel has one text per tab, which we need to update + UpdateFilterPanel(Sender); + + // Ensure controls are in a valid state + ValidateControls(Sender); + FixQueryTabCloseButtons; +end;} + + +{procedure TMainForm.PageControlMainChanging(Sender: TObject; var AllowChange: Boolean); +begin + // Leave editing mode on tab changes so the editor does not stay somewhere + if (ActiveGridEditor <> nil) + and Assigned(ActiveGridEditor.Tree) + and ActiveGridEditor.Tree.IsEditing then begin + LogSQL('Cancelling tree edit mode on '+ActiveGridEditor.Tree.Name, lcDebug); + ActiveGridEditor.Tree.CancelEditNode; + end; +end;} + + +{procedure TMainForm.PageControlHostChange(Sender: TObject); +var + tab: TTabSheet; + list: TBaseVirtualTree; +begin + tab := PageControlHost.ActivePage; + if tab = tabDatabases then list := ListDatabases + else if tab = tabVariables then list := ListVariables + else if tab = tabStatus then list := ListStatus + else if tab = tabProcesslist then list := ListProcesses + else if tab = tabCommandStats then list := ListCommandStats + else Exit; // Silence compiler warning + list.TrySetFocus; + UpdateFilterPanel(Sender); + PageControlTabHighlight(PageControlHost); +end;} + + +{procedure TMainForm.ListTablesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); +var + i, NumObj: Integer; + Obj: TDBObject; + Objects: TDBObjectList; + NumObjects: TStringList; + Msg: String; + vt: TVirtualStringTree; + Conn: TDBConnection; + ScrollOffset: TPoint; +begin + // DB-Properties + vt := Sender as TVirtualStringTree; + if vt.Tag = VTREE_LOADED then + Exit; + LogSQL('ListTablesBeforePaint', lcDebug); + Screen.Cursor := crHourGlass; + Conn := ActiveConnection; + ScrollOffset := vt.OffsetXY; + vt.BeginUpdate; + vt.Clear; + Msg := ''; + if Conn <> nil then begin + ShowStatusMsg(f_('Displaying objects from "%s" ...', [Conn.Database])); + Objects := Conn.GetDBObjects(Conn.Database, vt.Tag = VTREE_NOTLOADED_PURGECACHE, FActiveObjectGroup); + vt.RootNodeCount := Objects.Count; + + NumObjects := TStringList.Create; + FDBObjectsMaxSize := 1; + FDBObjectsMaxRows := 1; + for i:=0 to Objects.Count-1 do begin + Obj := Objects[i]; + NumObj := StrToIntDef(NumObjects.Values[Obj.ObjType], 0); + Inc(NumObj); + NumObjects.Values[Obj.ObjType] := IntToStr(NumObj); + if Obj.Size > FDBObjectsMaxSize then FDBObjectsMaxSize := Obj.Size; + if Obj.Rows > FDBObjectsMaxRows then FDBObjectsMaxRows := Obj.Rows; + end; + Msg := Conn.Database + ': ' + FormatNumber(Objects.Count) + ' '; + if NumObjects.Count = 1 then + Msg := Msg + LowerCase(NumObjects.Names[0]) + else + Msg := Msg + 'object'; + if Objects.Count <> 1 then Msg := Msg + 's'; + if (NumObjects.Count > 1) and (Objects.Count > 0) then begin + Msg := Msg + ' ('; + for i:=0 to NumObjects.Count-1 do begin + NumObj := StrToIntDef(NumObjects.ValueFromIndex[i], 0); + if NumObj = 0 then + Continue; + Msg := Msg + FormatNumber(NumObj) + ' ' + LowerCase(NumObjects.Names[i]); + if NumObj <> 1 then Msg := Msg + 's'; + Msg := Msg + ', '; + end; + Delete(Msg, Length(Msg)-1, 2); + Msg := Msg + ')'; + end; + end; + vt.OffsetXY := ScrollOffset; + vt.EndUpdate; + vt.Tag := VTREE_LOADED; + FListTablesSorted := False; + ShowStatusMsg(Msg, 0); + ShowStatusMsg; + ValidateControls(Self); + Screen.Cursor := crDefault; +end;} + + +{procedure TMainForm.ListTablesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); +var + Obj: PDBObject; +begin + if Column <> (Sender as TVirtualStringTree).Header.MainColumn then + Exit; + Obj := Sender.GetNodeData(Node); + case Kind of + ikNormal, ikSelected: + ImageIndex := Obj.ImageIndex; + ikOverlay: + ImageIndex := Obj.OverlayImageIndex; + end; +end;} + + +{procedure TMainForm.ListTablesGetNodeDataSize(Sender: TBaseVirtualTree; + var NodeDataSize: Integer); +begin + NodeDataSize := SizeOf(TDBObject); +end;} + + +{procedure TMainForm.ListTablesGetText(Sender: TBaseVirtualTree; + Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; + var CellText: string); +var + Obj: PDBObject; +begin + Obj := Sender.GetNodeData(Node); + CellText := ''; + case Column of + 0: begin + if Obj.Schema <> '' then + CellText := Obj.Schema + '.' + Obj.Name + else + CellText := Obj.Name; + if Sender.IsEditing and (Node = Sender.FocusedNode) then + CellText := Obj.Name; + end; + 1: if Obj.Rows > -1 then CellText := FormatNumber(Obj.Rows); + 2: if Obj.Size > -1 then CellText := FormatByteNumber(Obj.Size); + 3: CellText := DateTimeToStrDef(Obj.Created, ''); + 4: CellText := DateTimeToStrDef(Obj.Updated, ''); + 5: CellText := Obj.Engine; + 6: CellText := Obj.Comment; + 7: if Obj.Version > -1 then CellText := IntToStr(Obj.Version); + 8: CellText := Obj.RowFormat; + 9: if Obj.AvgRowLen > -1 then CellText := FormatByteNumber(Obj.AvgRowLen); + 10: if Obj.MaxDataLen > -1 then CellText := FormatByteNumber(Obj.MaxDataLen); + 11: if Obj.IndexLen > -1 then CellText := FormatByteNumber(Obj.IndexLen); + 12: if Obj.DataFree > -1 then CellText := FormatByteNumber(Obj.DataFree); + 13: if Obj.AutoInc > -1 then CellText := FormatNumber(Obj.AutoInc); + 14: if Obj.LastChecked <> 0 then CellText := DateTimeToStr(Obj.LastChecked); + 15: CellText := Obj.Collation; + 16: if Obj.Checksum > -1 then CellText := IntToStr(Obj.Checksum); + 17: CellText := Obj.CreateOptions; + 18: CellText := Obj.ObjType; + end; +end;} + + +{procedure TMainForm.ListTablesInitNode(Sender: TBaseVirtualTree; ParentNode, + Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); +var + Obj: PDBObject; + Objects: TDBObjectList; + Conn: TDBConnection; +begin + Conn := ActiveConnection; + Obj := Sender.GetNodeData(Node); + if (Conn <> nil) and (not Conn.Database.IsEmpty) then begin + Objects := Conn.GetDBObjects(Conn.Database, False, FActiveObjectGroup); + Obj^ := Objects[Node.Index]; + end else begin + Obj^ := nil; + LogSQL('InitNode on '+Sender.Name+' failed, due to no connection or no database set. Database: "'+Conn.Database+'"', lcDebug); + end; +end;} + + + +{*** + Selection in ListTables is changing +} +{procedure TMainForm.ListTablesChange(Sender: TBaseVirtualTree; Node: + PVirtualNode); +var + Msg: String; +begin + ValidateControls(Sender); + if ListTables.SelectedCount > 1 then + Msg := _('Selected') + ': ' + FormatNumber(ListTables.SelectedCount) + else + Msg := ''; + ShowStatusMsg(Msg, 1) +end;} + + +{*** + Enable/disable various buttons and menu items. + Invoked when + - active sheet changes + - highlighted database changes + ... +} +{procedure TMainForm.ValidateControls(Sender: TObject); +var + inDataTab, inDataOrQueryTab, inDataOrQueryTabNotEmpty, inGrid: Boolean; + HasConnection, GridHasChanges, EnableTimestamp: Boolean; + Grid: TVirtualStringTree; + inSynMemo, inSynMemoEditable: Boolean; + Results: TDBQuery; + RowNum: PInt64; + CellText: String; + Conn: TDBConnection; + ResultCol: Integer; +begin + // When adding some new TAction here, be sure to apply this procedure to its OnUpdate event + + Grid := ActiveGrid; + Conn := ActiveConnection; + HasConnection := Conn <> nil; + Results := nil; + GridHasChanges := False; + EnableTimestamp := False; + CellText := ''; + if HasConnection and Assigned(Grid) then begin + Results := GridResult(Grid); + ResultCol := Grid.FocusedColumn -1; + if (Results<>nil) and Assigned(Grid.FocusedNode) then begin + RowNum := Grid.GetNodeData(Grid.FocusedNode); + Results.RecNo := RowNum^; + GridHasChanges := Results.Modified or Results.Inserted; + if ResultCol > NoColumn then begin + EnableTimestamp := Results.DataType(ResultCol).Category in [dtcInteger, dtcReal]; + CellText := Results.Col(ResultCol, True); + end; + end; + end; + inDataTab := Grid = DataGrid; + inDataOrQueryTab := inDataTab or QueryTabs.HasActiveTab; + inDataOrQueryTabNotEmpty := inDataOrQueryTab and Assigned(Grid) and (Grid.RootNodeCount > 0); + inGrid := Assigned(Grid) and (ActiveControl = Grid); + + actFullRefresh.Enabled := HasConnection and (PageControlMain.ActivePage = tabDatabase); + actDataInsert.Enabled := HasConnection and inGrid and Assigned(Results); + actDataDuplicateRowWithoutKeys.Enabled := HasConnection and inGrid and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); + actDataDuplicateRowWithKeys.Enabled := actDataDuplicateRowWithoutKeys.Enabled; + actDataDelete.Enabled := HasConnection and inGrid and (Grid.SelectedCount > 0); + actDataFirst.Enabled := HasConnection and inDataOrQueryTabNotEmpty and inGrid; + actDataLast.Enabled := HasConnection and inDataOrQueryTabNotEmpty and inGrid; + actDataPostChanges.Enabled := HasConnection and GridHasChanges; + actDataCancelChanges.Enabled := HasConnection and GridHasChanges; + actDataSaveBlobToFile.Enabled := HasConnection and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); + actGridEditFunction.Enabled := HasConnection and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); + actDataPreview.Enabled := HasConnection and inDataOrQueryTabNotEmpty and Assigned(Grid.FocusedNode); + actDataOpenUrl.Enabled := (Length(CellText)<SIZE_MB) and ExecRegExpr('^(https?://[^\s]+|www\.\w\S+)$', CellText); + actUnixTimestampColumn.Enabled := HasConnection and inDataTab and EnableTimestamp; + actUnixTimestampColumn.Checked := inDataTab and HandleUnixTimestampColumn(Grid, Grid.FocusedColumn); + actPreviousResult.Enabled := HasConnection and QueryTabs.HasActiveTab and Assigned(QueryTabs.ActiveTab.ActiveResultTab); + actNextResult.Enabled := actPreviousResult.Enabled; + + // Activate export-options if we're in any list control + actExportData.Enabled := HasConnection; + actDataSetNull.Enabled := HasConnection and inDataOrQueryTab and Assigned(Results) and Assigned(Grid.FocusedNode); + + // Help only supported on regular MySQL and MariaDB servers + actSQLHelp.Enabled := HasConnection; + + inSynMemo := ActiveSynMemo(True) <> nil; + inSynMemoEditable := inSynMemo and (not ActiveSynMemo(True).ReadOnly); + actSaveSynMemoToTextfile.Enabled := inSynMemo; + actToggleComment.Enabled := inSynMemoEditable; + if inSynMemo then begin + actCut.Enabled := inSynMemoEditable; + actPaste.Enabled := inSynMemoEditable; + end else begin + actCut.Enabled := True; + actPaste.Enabled := True; + end; + + ValidateQueryControls(Sender); + UpdateLineCharPanel; + PageControlTabHighlight(PageControlMain); +end;} + + +{procedure TMainForm.ValidateQueryControls(Sender: TObject); +var + NotEmpty, HasSelection, HasConnection: Boolean; + Tab: TQueryTab; + cap: String; + InQueryTab, InEditorTab: Boolean; + Conn: TDBConnection; +begin + // Enable/disable TActions, according to the current window/connection state + + // Prevent superfluous calls while setting up query tabs + if not MainFormAfterCreateDone then + Exit; + + for Tab in QueryTabs do begin + cap := Trim(Tab.TabSheet.Caption); + if cap[Length(cap)] = '*' then + cap := Copy(cap, 1, Length(cap)-1); + if Tab.Memo.Modified then + cap := cap + '*'; + if Tab.TabSheet.Caption <> cap then + SetTabCaption(Tab.TabSheet.PageIndex, cap); + end; + InQueryTab := QueryTabs.HasActiveTab; + InEditorTab := PageControlMain.ActivePage = tabEditor; + Tab := QueryTabs.ActiveTab; + NotEmpty := InQueryTab and (Tab.Memo.GetTextLen > 0); + HasSelection := InQueryTab and Tab.Memo.SelAvail; + Conn := ActiveConnection; + HasConnection := Conn <> nil; + actExecuteQuery.Enabled := HasConnection and InQueryTab and NotEmpty and (not Tab.QueryRunning); + actExecuteSelection.Enabled := HasConnection and InQueryTab and HasSelection and (not Tab.QueryRunning); + actExecuteCurrentQuery.Enabled := actExecuteQuery.Enabled; + actExplainCurrentQuery.Enabled := actExecuteQuery.Enabled and (Conn.Parameters.NetTypeGroup in [ngMySQL, ngPgSQL, ngSQLite]); + actSaveSQLAs.Enabled := InQueryTab and NotEmpty; + actSaveSQL.Enabled := (actSaveSQLAs.Enabled and Tab.Memo.Modified) or InEditorTab; + actSaveSQLselection.Enabled := InQueryTab and HasSelection; + actSaveSQLSnippet.Enabled := InQueryTab and NotEmpty; + actSaveSQLSelectionSnippet.Enabled := InQueryTab and HasSelection; + actClearQueryEditor.Enabled := InQueryTab; + actSetDelimiter.Enabled := InQueryTab; + actCloseQueryTab.Enabled := IsQueryTab(PageControlMain.ActivePageIndex, False); + actCloseAllQueryTabs.Enabled := QueryTabs.Count > 1; + actCodeFoldingStartRegion.Enabled := InQueryTab; + actCodeFoldingEndRegion.Enabled := InQueryTab; + actCodeFoldingFoldSelection.Enabled := HasSelection; + if InQueryTab then begin + if HasConnection and (Conn.Parameters.SessionColor <> clNone) then begin + Tab.Memo.Gutter.Color := Conn.Parameters.SessionColor; + end + else begin + Tab.Memo.Gutter.Color := clBtnFace; + end; + end; + +end;} + + +{procedure TMainForm.KillProcess(Sender: TObject); +var + t: Boolean; + pid: Int64; + Node: PVirtualNode; + Conn: TDBConnection; +begin + t := TimerRefresh.Enabled; + TimerRefresh.Enabled := false; // prevent av (ListProcesses.selected...) + Conn := ActiveConnection; + if MessageDialog('Kill '+IntToStr(ListProcesses.SelectedCount)+' Process(es)?', mtConfirmation, [mbok,mbcancel]) = mrok then + begin + Node := GetNextNode(ListProcesses, nil, True); + while Assigned(Node) do begin + pid := StrToInt64Def(ListProcesses.Text[Node, ListProcesses.Header.MainColumn], 0); + // Don't kill own process + if pid = Conn.ThreadId then + LogSQL(f_('Ignoring own process id #%d when trying to kill it.', [pid])) + else try + Conn.Query(Conn.GetSQLSpecifity(spKillProcess, [pid])); + except + on E:EDbError do begin + if Conn.LastErrorCode <> ER_NO_SUCH_THREAD then + if MessageDialog(E.Message, mtError, [mbOK, mbAbort]) = mrAbort then + break; + end; + end; + Node := GetNextNode(ListProcesses, Node, True); + end; + InvalidateVT(ListProcesses, VTREE_NOTLOADED, True); + end; + TimerRefresh.Enabled := t; // re-enable autorefresh timer +end;} + + +{procedure TMainForm.SynCompletionProposalChange(Sender: TObject; + AIndex: Integer); +var + Proposal: TSynCompletionProposal; + SelectedFuncName: String; + SQLFunc: TSQLFunction; +begin + Proposal := Sender as TSynCompletionProposal; + if (AIndex >= 0) and (AIndex < Proposal.ItemList.Count) then begin + Proposal.Title := Proposal.InsertItem(AIndex); + // Show function description in hint panel + ShowStatusMsg('', 0);} + //SelectedFuncName := RegExprGetMatch('}function\\column\{\}\\color\{\w+\}([^\\]+)\\', Proposal.DisplayItem(AIndex), 1); + {if not SelectedFuncName.IsEmpty then begin + for SQLFunc in ActiveConnection.SQLFunctions do begin + if SQLFunc.Name.ToUpper = SelectedFuncName.ToUpper then begin + ShowStatusMsg(SQLFunc.Description.Replace(SLineBreak, ' '), 0); + Break; + end; + end; + end; + end; +end;} + + +{ Proposal about to insert a String into synmemo } +{procedure TMainForm.SynCompletionProposalCodeCompletion(Sender: TObject; + var Value: String; Shift: TShiftState; Index: Integer; EndToken: Char); +var + Proposal: TSynCompletionProposal; + rx: TRegExpr; + ImageIndex, f: Integer; + FunctionDeclaration: String; +begin + Proposal := Sender as TSynCompletionProposal; + // Surround identifiers with backticks if it is a column, table, routine, db + rx := TRegExpr.Create;} + //rx.Expression := '\\image\{(\d+)\}'; + {if rx.Exec(Proposal.ItemList[Index]) then begin + ImageIndex := MakeInt(rx.Match[1]); + if not (ImageIndex in [ICONINDEX_KEYWORD, ICONINDEX_FUNCTION, 113]) then begin + FunctionDeclaration := ''; + f := Pos('(', Value); + if f > 0 then begin + FunctionDeclaration := Copy(Value, f, Length(Value)); + Delete(Value, f, Length(Value)); + end; + + rx.Expression := '^(['+QuoteRegExprMetaChars(ActiveConnection.QuoteChars)+'])(.*)$'; + if rx.Exec(Value) then begin + // Left character of identifier is already a quote: user wants to force quoting. + // Seperate that left quote character away from what gets now quoted automatically, and force quoting + Value := ActiveConnection.QuoteIdent(rx.Match[2], True) + FunctionDeclaration; + end else begin + // Identifier without left quote - quote when required + Value := ActiveConnection.QuoteIdent(Value, False) + FunctionDeclaration; + end; + end; + end; + rx.Free; + Proposal.Form.CurrentEditor.UndoList.AddGroupBreak; + // Hide hint text added in .OnChange event + ShowStatusMsg('', 0); +end;} + + +{procedure TMainForm.SynCompletionProposalAfterCodeCompletion(Sender: TObject; + const Value: String; Shift: TShiftState; Index: Integer; EndToken: Char); +var + Proposal: TSynCompletionProposal; +begin + Proposal := Sender as TSynCompletionProposal; + Proposal.Form.CurrentEditor.UndoList.AddGroupBreak; + // Explicitly set focus again to work around a bug in Ultramon, see issue #2396 + Proposal.Form.CurrentEditor.SetFocus; +end;} + + +{ Proposal-Combobox pops up } +{procedure TMainForm.SynCompletionProposalExecute(Kind: SynCompletionType; + Sender: TObject; var CurrentInput: String; var x, y: Integer; + var CanExecute: Boolean); +var + i, j, ImageIndex, ColumnsInList: Integer; + Results: TDBQuery; + DBObjects: TDBObjectList; + CurrentQuery, TableClauses, TableName, LeftPart, Token1, Token2, Token3, Token, Ident: String; + Tables: TStringList; + rx: TRegExpr; + Start, TokenTypeInt: Integer; + Attri: TSynHighlighterAttributes; + Proposal: TSynCompletionProposal; + Editor: TCustomSynEdit; + Queries: TSQLBatch; + Query: TSQLSentence; + Conn: TDBConnection; + RoutineEditor: TfrmRoutineEditor; + Param: TRoutineParam; + DisplayText: String; + SQLFunc: TSQLFunction; + + procedure AddTable(Obj: TDBObject); + var + FunctionDeclaration: String; + FuncParams: TRoutineParamList; + FuncParam: TRoutineParam; + begin + // Append routine parameter declaration + FunctionDeclaration := ''; + if Obj.NodeType in [lntProcedure, lntFunction] then begin + FuncParams := TRoutineParamList.Create(True); + Obj.Connection.ParseRoutineStructure(Obj, FuncParams); + for FuncParam in FuncParams do begin + FunctionDeclaration := FunctionDeclaration + FuncParam.Name + ', '; + end; + if FunctionDeclaration <> '' then begin + Delete(FunctionDeclaration, Length(FunctionDeclaration)-1, 2); + FunctionDeclaration := '(' + FunctionDeclaration + ')'; + end; + FuncParams.Free; + end; + + DisplayText := SynCompletionProposalPrettyText(Obj.ImageIndex, _(LowerCase(Obj.ObjType)), Obj.Name, FunctionDeclaration); + Proposal.AddItem(DisplayText, Obj.Name+FunctionDeclaration); + end; + + procedure AddColumns(const LeftToken: String); + var + dbname, tblname: String; + Columns: TTableColumnList; + Col: TTableColumn; + Keys: TTableKeyList; + Key: TTableKey; + Obj: TDBObject; + ColumnIcon: Integer; + begin + dbname := ''; + tblname := LeftToken; + if Pos('.', tblname) > -1 then begin + dbname := Copy(tblname, 0, Pos('.', tblname)-1); + tblname := Copy(tblname, Pos('.', tblname)+1, Length(tblname)); + end; + // db and table name may already be quoted + if dbname = '' then + dbname := Conn.Database; + dbname := Conn.DeQuoteIdent(dbname); + tblname := Conn.DeQuoteIdent(tblname); + DBObjects := Conn.GetDBObjects(dbname); + for Obj in DBObjects do begin + if (Obj.Name.ToLowerInvariant = tblname.ToLowerInvariant) and (Obj.NodeType in [lntTable, lntView]) then begin + Columns := Obj.TableColumns; + Keys := Obj.TableKeys; + for Col in Columns do begin + // Detect index icon, if any + ColumnIcon := ICONINDEX_FIELD; + for Key in Keys do begin + if Key.Columns.Contains(Col.Name) then begin + ColumnIcon := Key.ImageIndex; + Break; + end; + end; + // Put formatted text and icon into proposal + DisplayText := SynCompletionProposalPrettyText(ColumnIcon, LowerCase(Col.DataType.Name), Col.Name, Col.Comment, DatatypeCategories[Col.DataType.Category].NullColor); + if CurrentInput.StartsWith(Conn.QuoteChar) then + Proposal.AddItem(DisplayText, Conn.QuoteChar + Col.Name) + else + Proposal.AddItem(DisplayText, Col.Name); + Inc(ColumnsInList); + end; + Columns.Free; + break; + end; + end; + end; + +begin + Proposal := Sender as TSynCompletionProposal; + Proposal.Font.Assign(Font); + Proposal.TitleFont.Size := Proposal.Font.Size; + Proposal.ItemHeight := ScaleSize(PROPOSAL_ITEM_HEIGHT); + Proposal.ClearList; + Proposal.Columns[0].ColumnWidth := ScaleSize(100); // Kind of random value, but fits well + Proposal.Columns[1].ColumnWidth := ScaleSize(100); + Conn := ActiveConnection; + Editor := Proposal.Form.CurrentEditor; + Editor.GetHighlighterAttriAtRowColEx(Editor.PrevWordPos, Token, TokenTypeInt, Start, Attri); + CanExecute := AppSettings.ReadBool(asCompletionProposal) and + (not (TtkTokenKind(TokenTypeInt) in [SynHighlighterSQL.tkString, SynHighlighterSQL.tkComment])); + if not CanExecute then + Exit; + + // Work around for issue #2640. See ApplicationDeActivate + Proposal.Form.Enabled := True; + + rx := TRegExpr.Create; + + // Find token1.token2.token3, while cursor is somewhere in token3 + Ident := '[^\s,\(\)=\.]'; + rx.Expression := '(('+Ident+'+)\.)?('+Ident+'+)\.('+Ident+'*)$'; + LeftPart := Copy(Editor.LineText, 1, Editor.CaretX-1); + if rx.Exec(LeftPart) then begin + Token1 := Conn.DeQuoteIdent(rx.Match[2]); + Token2 := Conn.DeQuoteIdent(rx.Match[3]); + Token3 := Conn.DeQuoteIdent(rx.Match[4]); + end; + + // Server variables, s'il vous plait? + rx.Expression := '^@@(SESSION|GLOBAL)$'; + rx.ModifierI := True; + if rx.Exec(Token2) then begin + try + Results := Conn.GetResults('SHOW '+UpperCase(rx.Match[1])+' VARIABLES'); + while not Results.Eof do begin + DisplayText := SynCompletionProposalPrettyText(ICONINDEX_PRIMARYKEY, _('Variable'), Results.Col(0), StringReplace(Results.Col(1), '\', '\\', [rfReplaceAll])); + Proposal.AddItem(DisplayText, Results.Col(0)); + Results.Next; + end; + except + // Just log error in sql log, do not disturb user while typing + end; + end else begin + // Get column names into the proposal pulldown + // when we write sql like "SELECT t.|col FROM table [AS] t" + // Current limitation: Identifiers (masked or not) containing + // spaces are not detected correctly. + + // 1. find currently edited sql query around the cursor position in synmemo + if Editor = SynMemoFilter then begin + // Make sure the below regexp can find structure + CurrentQuery := 'SELECT * FROM '+ActiveDbObj.QuotedName+' WHERE ' + Editor.Text; + end else begin + // In a query tab + Queries := TSQLBatch.Create; + Queries.SQL := Editor.Text; + for Query in Queries do begin + if (Query.LeftOffset <= Editor.SelStart) and (Editor.SelStart < Query.RightOffset) then begin + CurrentQuery := Query.SQLWithoutComments; + Break; + end; + end; + Queries.Free; + end; + + // 2. Parse FROM clause, detect relevant table/view, probably aliased + rx.ModifierG := True; + rx.ModifierI := True; + rx.Expression := '\b(FROM|INTO|UPDATE)\s+(IGNORE\s+)?(.+)(WHERE|HAVING|ORDER|GROUP)?'; + if rx.Exec(CurrentQuery) then begin + TableClauses := rx.Match[3]; + // Ensure tables in JOIN clause(s) are splitted by comma + TableClauses := ReplaceRegExpr('\sJOIN\s', TableClauses, ',', [rroModifierI]); + // Remove surrounding parentheses + TableClauses := StringReplace(TableClauses, '(', ' ', [rfReplaceAll]); + TableClauses := StringReplace(TableClauses, ')', ' ', [rfReplaceAll]); + // Split table clauses by commas + Tables := TStringList.Create; + Tables.Delimiter := ','; + Tables.StrictDelimiter := true; + Tables.DelimitedText := TableClauses; + rx.Expression := '(\S+)\s+(AS\s+)?(\S+)'; + + for i := 0 to Tables.Count - 1 do begin + // If the just typed word equals the alias of this table or the + // tablename itself, set tablename var and break loop + if rx.Exec(Tables[i]) then while true do begin + if Token2 = Conn.DeQuoteIdent(rx.Match[3]) then begin + TableName := rx.Match[1]; + break; + end; + if not rx.ExecNext then + break; + end; + if TableName <> '' then + break; + end; + end; + + ColumnsInList := 0; + if TableName <> '' then + AddColumns(TableName) + else if Token1 <> '' then + AddColumns(Conn.QuoteIdent(Token1, False)+'.'+Conn.QuoteIdent(Token2, False)) + else if Token2 <> '' then + AddColumns(Conn.QuoteIdent(Token2, False)); + + if Token1 = '' then begin + i := Conn.AllDatabases.IndexOf(Token2); + if i > -1 then begin + // Tables from specific database + Screen.Cursor := crHourGlass; + DBObjects := Conn.GetDBObjects(Conn.AllDatabases[i]); + Conn.PrefetchCreateCode(DBObjects); + for j:=0 to DBObjects.Count-1 do + AddTable(DBObjects[j]); + Conn.PurgePrefetchResults; + Screen.Cursor := crDefault; + end; + end; + + if Token2 = '' then begin + + // Column names from selected table, in data filter memo. + // For query memo only if no columns were added from left side table. + if ColumnsInList = 0 then begin + // Avoid usage of .QuotedName so we don't get the schema in it, see https://www.heidisql.com/forum.php?t=35411 + AddColumns(Conn.QuoteIdent(ActiveDbObj.Name)); + end; + + // All databases + for i:=0 to Conn.AllDatabases.Count-1 do begin + DisplayText := SynCompletionProposalPrettyText(ICONINDEX_DB, _('database'), Conn.AllDatabases[i], ''); + Proposal.AddItem(DisplayText, Conn.AllDatabases[i]); + end; + + // Tables from current db + if Conn.Database <> '' then begin + DBObjects := Conn.GetDBObjects(Conn.Database); + Conn.PrefetchCreateCode(DBObjects); + for j:=0 to DBObjects.Count-1 do + AddTable(DBObjects[j]); + Conn.PurgePrefetchResults; + if Token1 <> '' then // assume that we have already a dbname in memo + Proposal.Position := Conn.AllDatabases.Count; + end; + + // Functions + for SQLFunc in Conn.SQLFunctions do begin + DisplayText := SynCompletionProposalPrettyText(ICONINDEX_FUNCTION, _('function'), SQLFunc.Name, SQLFunc.Declaration); + Proposal.AddItem(DisplayText, SQLFunc.Name + SQLFunc.Declaration); + end; + + + // Keywords + for i:=0 to MySQLKeywords.Count-1 do begin + DisplayText := SynCompletionProposalPrettyText(ICONINDEX_KEYWORD, _('keyword'), MySQLKeywords[i], ''); + Proposal.AddItem(DisplayText, MySQLKeywords[i]); + end; + + // Procedure params + if GetParentFormOrFrame(Editor) is TfrmRoutineEditor then begin + RoutineEditor := GetParentFormOrFrame(Editor) as TfrmRoutineEditor; + for Param in RoutineEditor.Parameters do begin + if Param.Context = 'IN' then ImageIndex := 120 + else if Param.Context = 'OUT' then ImageIndex := 121 + else if Param.Context = 'INOUT' then ImageIndex := 122 + else ImageIndex := -1; + DisplayText := SynCompletionProposalPrettyText(ImageIndex, Param.Datatype, Param.Name, ''); + Proposal.AddItem(DisplayText, Param.Name); + end; + end; + + end; + + end; + rx.Free; + +end;} + + +{procedure TMainForm.SynMemoQueryScanForFoldRanges(Sender: TObject; + FoldRanges: TSynFoldRanges; LinesToScan: TStrings; FromLine, ToLine: Integer); +var + Line: Integer; + LineText: String; +begin + // Code folding detection based on keywords in beginning of lines + for Line:=FromLine to ToLine do begin + LineText := LinesToScan[Line].TrimLeft; + if LineText.StartsWith('#region', True) then + FoldRanges.StartFoldRange(Line+1, FoldRegionType) + else if LineText.StartsWith('#endregion', True) then + FoldRanges.StopFoldRange(Line+1, FoldRegionType) + else + FoldRanges.NoFoldInfo(Line+1); + end; +end;} + + +{procedure TMainForm.SynMemoQuerySpecialLineColors(Sender: TObject; + Line: Integer; var Special: Boolean; var FG, BG: TColor); +var + Edit: TSynMemo; + Tab: TQueryTab; +begin + // Paint error line with red background + Edit := Sender as TSynMemo; + Tab := QueryTabs.TabByControl(Edit); + if Tab <> QueryTabs.ActiveTab then + Exit; + if Line = Tab.ErrorLine then begin + Special := True; + FG := ErrorLineForeground; + BG := ErrorLineBackground; + end; +end;} + + +{procedure TMainForm.SynMemoSQLLogSpecialLineColors(Sender: TObject; + Line: Integer; var Special: Boolean; var FG, BG: TColor); +var + Edit: TSynMemo; + LineText, Search: String; +begin + // Paint error line with red background, or warning in orange + Edit := Sender as TSynMemo; + LineText := Copy(Edit.Lines[Line-1], 1, 100); + Search := _(MsgSQLError); + Search := Copy(Search, 1, Pos('%', Search)-1); + //Logsql(LineText+' ::: '+Search); + if LineText.Contains(Search) then begin + Special := True; + FG := ErrorLineForeground; + BG := ErrorLineBackground; + end + else if LineText.Contains(_(SLogPrefixWarning)+':') then begin + Special := True; + FG := WarningLineForeground; + BG := WarningLineBackground; + end + else if LineText.Contains(_(SLogPrefixNote)+':') then begin + Special := True; + FG := NoteLineForeground; + BG := NoteLineBackground; + end + else if LineText.Contains(_(SLogPrefixInfo)+':') then begin + Special := True; + FG := InfoLineForeground; + BG := InfoLineBackground; + end; +end;} + + +{procedure TMainForm.SynMemoQueryStatusChange(Sender: TObject; Changes: TSynStatusChanges); +var + Edit: TSynMemo; + Tab: TQueryTab; + ContentOrCursor: Boolean; +begin + if not MainFormAfterCreateDone then + Exit; + + Edit := Sender as TSynMemo; + Tab := QueryTabs.TabByControl(Edit); + if Tab <> QueryTabs.ActiveTab then + Exit; + + ContentOrCursor := (scCaretX in Changes) or (scCaretY in Changes) or (scModified in Changes); + if ContentOrCursor then begin + // Disable error marker + Tab.ErrorLine := -1; + + // Check if bind param detection is enabled for text size <1M + // Uncheck checkbox if it's bigger + // Code moved back from TQueryTab.MemoOnChange here + Tab.TimerLastChange.Enabled := False; + Tab.FLastChange := Now; + Tab.TimerLastChange.Enabled := True; + + // Don't ask for saving empty contents. See issue #614 + if Edit.GetTextLen = 0 then begin + Tab.MemoFilename := ''; + Tab.Memo.Modified := False; + end; + + // Update various controls + ValidateQueryControls(Sender); + + UpdateLineCharPanel; + end; +end;} + + +{procedure TMainForm.SynMemoQueryTokenHint(Sender: TObject; Coords: TBufferCoord; + const Token: string; TokenType: Integer; Attri: TSynHighlighterAttributes; + var HintText: string); +var + SQLFunc: TSQLFunction; + Conn: TDBConnection; + AllObjects: TDBObjectList; + Obj: TDBObject; + i, ColumnNameChars: Integer; + Column: TTableColumn; + Parameters: TRoutineParamList; + Params: TStringList; + Param: TRoutineParam; +begin + // Activate hint for SQL function in query editors + Conn := ActiveConnection; + if Assigned(Conn) then begin + case TtkTokenKind(TokenType) of + + SynHighlighterSQL.tkFunction: begin + for SQLFunc in ActiveConnection.SQLFunctions do begin + if SQLFunc.Name.ToUpper = Token.ToUpper then begin + HintText := SQLFunc.Name + SQLFunc.Declaration + sLineBreak + sLineBreak + SQLFunc.Description; + Break; + end; + end; + end; + + SynHighlighterSQL.tkTableName: begin + // Show some details from table listing cache + if (not Conn.IsLockedByThread) and Conn.DbObjectsCached(Conn.Database) then begin + AllObjects := Conn.GetDBObjects(Conn.Database); + for Obj in AllObjects do begin + if (Obj.NodeType = lntTable) and (Obj.Name.ToLower = Token.ToLower) then begin + HintText := _(Obj.ObjType) + ' ' + Obj.Name + ':' + sLineBreak + + _('Rows') + ': ' + FormatNumber(Obj.Rows) + sLineBreak + + _('Size') + ': ' + FormatByteNumber(Obj.DataLen + Obj.IndexLen) + SLineBreak; + ColumnNameChars := 0; + for Column in Obj.TableColumns do begin + ColumnNameChars := Max(ColumnNameChars, Length(Column.Name)); + end; + for Column in Obj.TableColumns do begin + HintText := HintText + Format('%s%'+ColumnNameChars.ToString+'s: %s', [SLineBreak, Column.Name, Column.FullDataType]); + end; + + Break; + end; + end; + end; + end; + + SynHighlighterSQL.tkProcName: begin + // Show routine parameters, comment and body + if (not Conn.IsLockedByThread) and Conn.DbObjectsCached(Conn.Database) then begin + AllObjects := Conn.GetDBObjects(Conn.Database); + for Obj in AllObjects do begin + if (Obj.NodeType in [lntFunction, lntProcedure]) and (Obj.Name.ToLower = Token.ToLower) then begin + Parameters := TRoutineParamList.Create; + Conn.ParseRoutineStructure(Obj, Parameters); + HintText := _(Obj.ObjType) + ' ' + Obj.Name; + Params := TStringList.Create; + for Param in Parameters do begin + Params.Add(Param.Name + ' ['+Param.Datatype+']'); + end; + HintText := HintText + '(' + Implode(', ', Params) + ')' + sLineBreak + sLineBreak; + Params.Free; + if not Obj.Returns.IsEmpty then + HintText := HintText + 'Returns: ' + Obj.Returns + sLineBreak + sLineBreak; + if not Obj.Comment.IsEmpty then + HintText := HintText + Obj.Comment + sLineBreak + sLineBreak; + if not Obj.Body.IsEmpty then + HintText := HintText + StrEllipsis(Obj.Body, SIZE_KB); + HintText := Trim(HintText); + Break; + end; + end; + end; + end; + + SynHighlighterSQL.tkDatatype: begin + for i:=Low(Conn.Datatypes) to High(Conn.Datatypes) do begin + if Conn.Datatypes[i].Name.ToLower = Token.ToLower then begin + HintText := WrapText(Conn.Datatypes[i].Description, 100); + Break; + end; + end; + end; + + SynHighlighterSQL.tkString: begin + HintText := _('String:') + ' ' + FormatByteNumber(Length(Token)); + end; + + end; + end; +end;} + +{procedure TMainForm.TimerHostUptimeTimer(Sender: TObject); +var + Conn: TDBConnection; + Uptime: Integer; + ServerNow: TDateTime; + ServerNowStr: String; +begin + // Display server uptime and current date time + Conn := ActiveConnection; + if Assigned(Conn) then begin + Uptime := Conn.ServerUptime; + if Uptime >= 0 then + ShowStatusMsg(_('Uptime')+': '+FormatTimeNumber(Conn.ServerUptime, False), 4) + else + ShowStatusMsg(_('Uptime')+': '+_('unknown'), 4); + + ServerNow := Conn.ServerNow; + if ServerNow >= 0 then begin + DateTimeToString(ServerNowStr, 't', ServerNow); + ShowStatusMsg(f_('Server time: %s', [ServerNowStr]), 5) + end else + ShowStatusMsg(f_('Server time: %s', [_('unknown')]), 5); + end else begin + ShowStatusMsg('', 4); + end; + +end;} + + +{procedure TMainForm.TimerRefreshTimer(Sender: TObject); +begin + // Auto-refreshing grid or list. Only if main form is active, to prevent issues like #669 + if Screen.ActiveForm = Self then + actRefresh.Execute; +end;} + + +{procedure TMainForm.ListTablesEditing(Sender: TBaseVirtualTree; + Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); +var + Obj: PDBObject; +begin + // Tables and views can be renamed, routines cannot + if Assigned(Node) then begin + Obj := Sender.GetNodeData(Node); + Allowed := Obj.NodeType in [lntTable, lntView]; + end; +end;} + + +{*** + Rename table after checking the new name for invalid characters +} +{procedure TMainForm.ListTablesNewText(Sender: TBaseVirtualTree; Node: + PVirtualNode; Column: TColumnIndex; NewText: String); +var + Obj: PDBObject; + sql: String; +begin + // Fetch data from node + Obj := Sender.GetNodeData(Node); + + // Try to rename, on any error abort and don't rename ListItem + try + // rename table + case Obj.NodeType of + lntTable: + sql := Obj.Connection.GetSQLSpecifity(spRenameTable); + lntView: + sql := Obj.Connection.GetSQLSpecifity(spRenameView); + else + raise EDbError.Create('Cannot rename '+Obj.ObjType); + end; + + sql := Format(sql, [Obj.QuotedName(True, False), Obj.Connection.QuoteIdent(NewText)]); + Obj.Connection.Query(sql); + + if SynSQLSynUsed.TableNames.IndexOf( NewText ) = -1 then begin + SynSQLSynUsed.TableNames.Add(NewText); + end; + // Update nodedata + Obj.Name := NewText; + Obj.UnloadDetails; + // Now the active tree db has to be updated. But calling RefreshTreeDB here causes an AV + // so we do it manually here + DBTree.InvalidateChildren(FindDBNode(DBtree, Obj.Connection, Obj.Database), True); + except + on E:EDbError do + ErrorDialog(E.Message); + end; +end;} + + +{procedure TMainForm.TimerConnectedTimer(Sender: TObject); +var + ConnectedTime: Integer; + Conn: TDBConnection; +begin + Conn := ActiveConnection; + if (Conn <> nil) and Conn.Active then begin + // Calculate and display connection-time. Also, on any connect or reconnect, update server version panel. + ConnectedTime := Conn.ConnectionUptime; + ShowStatusMsg(_('Connected')+': ' + FormatTimeNumber(ConnectedTime, False), 2); + end else begin + ShowStatusMsg(_('Disconnected'), 2); + end; +end;} + + +{procedure TMainForm.Copylinetonewquerytab1Click(Sender: TObject); +var + Tab: TQueryTab; + LineText: String; +begin + // Create new query tab with current line in SQL log. This is for lazy mouse users. + if actNewQueryTab.Execute then begin + Tab := QueryTabs[MainForm.QueryTabs.Count-1]; + LineText := SynMemoSQLLog.LineText; + if AppSettings.ReadBool(asLogTimestamp) then + LineText := ReplaceRegExpr('^\s*\[[^\]]+\]\s', LineText, ''); + Tab.Memo.Text := LineText; + end; +end;} + + +{procedure TMainForm.QuickFilterClick(Sender: TObject); +var + Filter, Val, Col: String; + TableCol: TTableColumn; + Act: TAction; + Item: TMenuItem; + Conn: TDBConnection; + ShiftKeyPressed: Boolean; +begin + // Set filter for "where..."-clause + if (PageControlMain.ActivePage <> tabData) or (DataGrid.FocusedColumn = NoColumn) then + Exit; + + Filter := ''; + Conn := ActiveConnection; + ShiftKeyPressed := KeyPressed(VK_SHIFT); + + if Sender is TAction then begin + // Normal case for most quick filters + Act := Sender as TAction; + if ExecRegExpr('Prompt\d+$', Act.Name) then begin + // Item needs prompt + TableCol := SelectedTableFocusedColumn; + Col := Conn.QuoteIdent(TableCol.Name, False); + + if (TableCol.DataType.Index = dbdtJson) + and (Conn.Parameters.NetTypeGroup = ngPgSQL) then begin + Col := Col + '::text'; + end; + Val := DataGrid.Text[DataGrid.FocusedNode, DataGrid.FocusedColumn]; + if InputQuery(_('Specify filter-value...'), Act.Caption, Val) then begin + if Act = actQuickFilterPrompt1 then + Filter := Col + ' = ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt2 then + Filter := Col + ' != ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt3 then + Filter := Col + ' > ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt4 then + Filter := Col + ' < ' + Conn.EscapeString(Val, TableCol.DataType) + else if Act = actQuickFilterPrompt5 then + Filter := Conn.GetSQLSpecifity(spLikeCompare, [Col, Conn.EscapeString('%'+Val+'%', TableCol.DataType)]); + end; + end + else begin + Filter := Act.Hint; + end; + end + else if Sender is TMenuItem then begin + // Sender is one of the subitems in "More values" menu + Item := Sender as TMenuItem; + Filter := Item.Hint; + end; + + if Filter <> '' then begin + if ExecRegExpr('\s+LIKE\s+''', Filter) then + Filter := Filter + Conn.LikeClauseTail; + + SynMemoFilter.UndoList.AddGroupBreak; + SynMemoFilter.SelectAll; + if ShiftKeyPressed + and (Pos(Filter, SynMemoFilter.Text) = 0) and (Pos(SynMemoFilter.Text, Filter) = 0) + and (not SynMemoFilter.Text.Trim.IsEmpty) + then begin + SynMemoFilter.SelText := SynMemoFilter.Text + ' AND ' + Filter + end else begin + SynMemoFilter.SelText := Filter; + end; + ToggleFilterPanel(True); + actApplyFilterExecute(Sender); + end; +end;} + + +{procedure TMainForm.popupQueryPopup(Sender: TObject); +var + SQLFuncs: TSQLFunctionList; + i, j: Integer; + miGroup, miFunction: TMenuItem; +begin + // Sets cursor into memo and activates TAction(s) like paste + QueryTabs.ActiveMemo.SetFocus; + // Create function menu items in popup menu + menuQueryInsertFunction.Clear; + SQLFuncs := ActiveConnection.SQLFunctions; + for i:=0 to SQLFuncs.Categories.Count-1 do begin + // Create a menu item which gets subitems later + miGroup := TMenuItem.Create(popupQuery); + miGroup.Caption := SQLFuncs.Categories[i]; + menuQueryInsertFunction.Add(miGroup); + for j:=0 to SQLFuncs.Count-1 do begin + if SQLFuncs[j].Category <> SQLFuncs.Categories[i] then + Continue; + miFunction := TMenuItem.Create(popupQuery); + miFunction.Caption := SQLFuncs[j].Name; + miFunction.ImageIndex := 13; + // Prevent generating a hotkey + miFunction.Caption := StringReplace(miFunction.Caption, '&', '&&', [rfReplaceAll]); + // Prevent generating a seperator line + if miFunction.Caption = '-' then + miFunction.Caption := '&-'; + miFunction.Hint := SQLFuncs[j].Name + SQLFuncs[j].Declaration + ' - ' + StrEllipsis(SQLFuncs[j].Description, 200); + // Prevent generating a seperator for ShortHint and LongHint + miFunction.Hint := StringReplace( miFunction.Hint, '|', '¦', [rfReplaceAll] ); + miFunction.Tag := j; + // Place menuitem on menu + miFunction.OnClick := insertFunction; + miGroup.Add(miFunction); + end; + end; +end;} + + +{procedure TMainForm.popupSqlLogPopup(Sender: TObject); +begin + // Update popupMenu items + menuLogToFile.Checked := FLogToFile; + menuOpenLogFolder.Enabled := FLogToFile; +end;} + + +{procedure TMainForm.AutoRefreshSetInterval(Sender: TObject); +var + SecondsStr: String; + Seconds: Extended; +begin + // set interval for autorefresh-timer + SecondsStr := FloatToStr(TimerRefresh.interval div 1000); + if InputQuery(_('Auto refresh'),_('Refresh list every ... second(s):'), SecondsStr) then begin + Seconds := StrToFloatDef(SecondsStr, 0); + if Seconds > 0 then begin + TimerRefresh.Interval := Trunc(Seconds * 1000); + TimerRefresh.Enabled := true; + menuAutoRefresh.Checked := true; + end + else + ErrorDialog(f_('Seconds must be between 0 and %d.', [maxint])); + end; +end;} + +{procedure TMainForm.AutoRefreshToggle(Sender: TObject); +begin + // enable autorefresh-timer + TimerRefresh.Enabled := not TimerRefresh.Enabled; + menuAutoRefresh.Checked := TimerRefresh.Enabled; +end;} + +{procedure TMainForm.SynMemoQueryDragOver(Sender, Source: TObject; X, + Y: Integer; State: TDragState; var Accept: Boolean); +var + src : TControl; + Memo: TSynMemo; + H: TVirtualStringTree; +begin + // dragging an object over the query-memo + Memo := QueryTabs.ActiveMemo; + src := Source as TControl; + // Accepting drag's from the same editor, from DBTree and from QueryHelpers + H := QueryTabs.ActiveHelpersTree; + Accept := (src = DBtree) or ((src = H) and Assigned(H.FocusedNode) and (H.GetNodeLevel(H.FocusedNode) in [1,2])); + // set x-position of cursor + Memo.CaretX := (x - Memo.Gutter.Width) div Memo.CharWidth - 1 + Memo.LeftChar; + // set y-position of cursor + Memo.CaretY := y div Memo.LineHeight + Memo.TopLine; + if not Memo.Focused then + Memo.SetFocus; +end;} + + +{procedure TMainForm.SynMemoQueryDragDrop(Sender, Source: TObject; X, + Y: Integer); +var + src : TControl; + Text, ItemText: String; + ShiftPressed: Boolean; + Tree: TVirtualStringTree; + Node: PVirtualNode; + History: TQueryHistory; +begin + // dropping a tree node or listbox item into the query-memo + QueryTabs.ActiveMemo.UndoList.AddGroupBreak; + src := Source as TControl; + Text := ''; + ShiftPressed := KeyPressed(VK_SHIFT); + Tree := QueryTabs.ActiveHelpersTree; + // Check for allowed controls as source has already + // been performed in OnDragOver. So, only do typecasting here. + if src = DBtree then begin + // Insert table or database name. If a table is dropped and Shift is pressed, prepend the db name. + case ActiveDbObj.NodeType of + lntDb: Text := ActiveDbObj.QuotedDatabase(False); + lntTable..lntEvent: begin + if ShiftPressed then + Text := ActiveDbObj.QuotedDatabase(False) + '.'; + Text := Text + ActiveDbObj.QuotedName(False); + end; + end; + end else if src = Tree then begin + case Tree.GetNodeLevel(Tree.FocusedNode) of + 1: + case Tree.FocusedNode.Parent.Index of + TQueryTab.HelperNodeSnippets: + Text := ReadTextFile(AppSettings.DirnameSnippets + Tree.Text[Tree.FocusedNode, 0] + '.sql', nil); + TQueryTab.HelperNodeHistory: + Text := ''; + else begin + Node := Tree.GetFirstChild(Tree.FocusedNode.Parent); + while Assigned(Node) do begin + if Tree.Selected[Node] then begin + ItemText := Tree.Text[Node, 0]; + if Node.Parent.Index = TQueryTab.HelperNodeColumns then + ItemText := ActiveConnection.QuoteIdent(ItemText, False); // Quote column names + if ShiftPressed then + Text := Text + ItemText + ',' + CRLF + else + Text := Text + ItemText + ', '; + end; + Node := Tree.GetNextSibling(Node); + end; + Delete(Text, Length(Text)-1, 2); + end; + end; + 2: + case Tree.FocusedNode.Parent.Parent.Index of + TQueryTab.HelperNodeHistory: begin + History := QueryTabs.ActiveTab.HistoryDays.Objects[Tree.FocusedNode.Parent.Index] as TQueryHistory; + Text := History[Tree.FocusedNode.Index].SQL; + end; + end; + end; + end else + raise Exception.Create(_('Unspecified source control in drag''n drop operation!')); + + if Text <> '' then begin + QueryTabs.ActiveMemo.SelText := Text; + QueryTabs.ActiveMemo.UndoList.AddGroupBreak; + // Requires to set focus, as doubleclick actions also call this procedure + QueryTabs.ActiveMemo.SetFocus; + end; +end;} + + + +{procedure TMainForm.SynMemoQueryDropFiles(Sender: TObject; X, Y: Integer; + AFiles: TUnicodeStrings); +var + i: Integer; + Tab: TQueryTab; +begin + // One or more files from explorer or somewhere else was dropped onto the + // query-memo - load their contents into seperate tabs + if not RunQueryFiles(AFiles, nil, False) then begin + for i:=0 to AFiles.Count-1 do begin + Tab := GetOrCreateEmptyQueryTab(True); + Tab.LoadContents(AFiles[i], False, nil); + end; + end; +end;} + + +{procedure TMainForm.SynMemoQueryKeyPress(Sender: TObject; var Key: Char); +var + Editor: TSynMemo; + Token, Replacement: String; + Attri: TSynHighlighterAttributes; + OldCaretXY, StartOfTokenRowCol, EndOfTokenRowCol, CurrentRowCol: TBufferCoord; + TokenTypeInt, Start, CurrentCharIndex: Integer; + //OldSelStart, OldSelEnd: Integer; + LineWithToken: String; + TableIndex, ProcIndex: Integer; + OldOnChange: TNotifyEvent; +const + WordChars = ['A'..'Z', 'a'..'z', '_']; + IgnoreChars = [#8]; // Backspace, and probably more which should not trigger uppercase +begin + // Uppercase reserved words, functions and data types + if CharInSet(Key, WordChars) or CharInSet(Key, IgnoreChars) then + Exit; + if not AppSettings.ReadBool(asAutoUppercase) then + Exit; + Editor := Sender as TSynMemo; + CurrentCharIndex := Editor.RowColToCharIndex(Editor.CaretXY); + // Go one left on trailing line feed, after which PrevWordPos doesn't work + Dec(CurrentCharIndex, 1); + CurrentRowCol := Editor.CharIndexToRowCol(CurrentCharIndex); + StartOfTokenRowCol := Editor.PrevWordPosEx(CurrentRowCol); + Editor.GetHighlighterAttriAtRowColEx(StartOfTokenRowCol, Token, TokenTypeInt, Start, Attri); + Replacement := UpperCase(Token); + + // Check if token is preceded by a dot, so it is most probably a table, column or some alias + LineWithToken := Editor.Lines[StartOfTokenRowCol.Line-1]; + if (StartOfTokenRowCol.Char > 1) and (LineWithToken[StartOfTokenRowCol.Char-1] = '.') then begin + Exit; + end; + + // Auto-fix case of known database objects + TableIndex := SynSQLSynUsed.TableNames.IndexOf(Token); + ProcIndex := SynSQLSynUsed.ProcNames.IndexOf(Token); + if TableIndex > -1 then begin + Replacement := SynSQLSynUsed.TableNames[TableIndex]; + end else if ProcIndex > -1 then begin + Replacement := SynSQLSynUsed.ProcNames[ProcIndex]; + end else if not (TtkTokenKind(TokenTypeInt) in [tkDatatype, tkFunction, tkKey]) then begin + // Only uppercase certain types of keywords + Exit; + end; + + if Token <> Replacement then begin + OldCaretXY := Editor.CaretXY; + //OldSelStart := Editor.SelStart; + //OldSelEnd := Editor.SelEnd; + + EndOfTokenRowCol := Editor.WordEndEx(StartOfTokenRowCol); + OldOnChange := Editor.OnChange; + Editor.OnChange := nil; + Editor.InsertBlock(StartOfTokenRowCol, EndOfTokenRowCol, PWideChar(Replacement), True); + Editor.OnChange := OldOnChange; + + Editor.CaretXY := OldCaretXY; + //Editor.SelStart := OldSelStart; // breaks at least some undo steps + //Editor.SelEnd := OldSelEnd; + end; + +end;} + + +{procedure TMainForm.AnySynMemoMouseWheel(Sender: TObject; Shift: TShiftState; + WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean); +var + Editor: TSynEdit; + NewFontSize: Integer; +begin + // Change font size with MouseWheel + // TODO: broken in high-dpi mode, just zooms in + if KeyPressed(VK_CONTROL) and AppSettings.ReadBool(asWheelZoom) then begin + Editor := TSynEdit(Sender); + NewFontSize := Editor.Font.Size; + if WheelDelta > 0 then + Inc(NewFontSize) + else + Dec(NewFontSize); + NewFontSize := Max(NewFontSize, 1); + AppSettings.ResetPath; + AppSettings.WriteInt(asFontSize, NewFontSize); + Editor.Font.Size := NewFontSize; + SetupSynEditors; + Handled := True; + end else begin + Handled := False; + end; +end;} + + +{procedure TMainForm.SynMemoQueryPaintTransient(Sender: TObject; Canvas: TCanvas; TransientType: TTransientType); +var + Editor : TSynEdit; + BufCrd: TBufferCoord; + Pix: TPoint; + DisCrd: TDisplayCoord; + Pnt: TPoint; + S: String; + I, SearchPos, CharIndex: Integer; + Attri: TSynHighlighterAttributes; + SelStart: Integer; + TmpCharA, TmpCharB: Char; + rx: TRegExpr; + SelWord, Line: String; +const} + //BracketChars: TSysCharSet = ['{','[','(','<','}',']',')','>']; + //OpenChars: Array of Char = ['{','[','(','<']; + //CloseChars: Array of Char = ['}',']',')','>']; + { + function CharToPixels(P: TBufferCoord): TPoint; + begin + Result := Editor.RowColumnToPixels(Editor.BufferToDisplayPos(P)); + end; +begin + if not MainFormCreated then + Exit; + if (FMatchingBraceBackgroundColor = clNone) and (FMatchingBraceForegroundColor = clNone) then + Exit; + if FSynEditInOnPaintTransient then + Exit; + FSynEditInOnPaintTransient := True; + + Editor := TSynEdit(Sender); + // Check for Editor.GetTextLen causes some endless loop in SynEdit. + // But not due to activated WordWrap. Must be some interlocked WM_GETTEXTLENGTH message, or an undefined text length. + //if Editor.GetTextLen > 5*SIZE_MB then + // Exit; + + // Highlight matching words, if selected text is a (small) word + if Editor.SelLength < Editor.CharsInWindow then begin + SelWord := Editor.SelText; + BufCrd := Editor.CaretXY; + CharIndex := Editor.RowColToCharIndex(BufCrd); + // Ensure GetWordAtRowCol finds the word by moving Char to the left of the selection + BufCrd.Char := Max(0, BufCrd.Char - (CharIndex - Editor.SelStart)); + if SelWord <> FLastSelWordInEditor then + Editor.Invalidate; // causes lots of additional implicit calls to OnPaintTransient + FLastSelWordInEditor := SelWord; + if (not SelWord.IsEmpty) and (SelWord = Editor.GetWordAtRowCol(BufCrd)) then begin + rx := TRegExpr.Create; + rx.Expression := '\b(' + QuoteRegExprMetaChars(SelWord) + ')\b'; + rx.ModifierI := True; + // Note: TopLine is wrong when lines are soft-wrapped, so we use RowToLine + for i:=Editor.RowToLine(Editor.TopLine) to Editor.RowToLine(Editor.TopLine + Editor.LinesInWindow) do begin + Line := Editor.Lines[i-1]; + if rx.Exec(Line) then while True do begin + SearchPos := rx.MatchPos[1]; + BufCrd := BufferCoord(SearchPos, i); + DisCrd := Editor.BufferToDisplayPos(BufCrd); + Pnt := Editor.RowColumnToPixels(DisCrd); + if (not Editor.IsPointInSelection(BufCrd)) // Found match is not the selection itself + and Editor.GetHighlighterAttriAtRowCol(BufCrd, SelWord, Attri) + then begin + //logsql(SelWord+': '+Attri.FriendlyName); + Canvas.Font.Size := Editor.Font.Size; + Canvas.Font.Style := Attri.Style; + // Todo: check if we need to handle TransientType ttAfter and ttBefore + Canvas.Font.Color:= FMatchingBraceForegroundColor; + Canvas.Brush.Color:= FMatchingBraceBackgroundColor; + if Canvas.Font.Color = clNone then + Canvas.Font.Color := Editor.Font.Color; + if Canvas.Brush.Color = clNone then + Canvas.Brush.Color := Editor.Color; + Canvas.TextOut(Pnt.X, Pnt.Y, rx.Match[1]); + end; + if not rx.ExecNext then + Break; + end; + end; + rx.Free; + end; + end; + + // Highlight matching brackets, only without selection + if not Editor.SelAvail then begin + + BufCrd := Editor.CaretXY; + SelStart := Editor.SelStart; + + if (SelStart > 0) and (SelStart <= Editor.GetTextLen) then + TmpCharA := Editor.Text[SelStart] + else + TmpCharA := #0; + + if (SelStart >= 0) and (SelStart < Editor.GetTextLen) then + TmpCharB := Editor.Text[SelStart + 1] + else + TmpCharB := #0; + + if CharInSet(TmpCharA, BracketChars) or CharInSet(TmpCharB, BracketChars) then begin + S := TmpCharB; + if not CharInSet(TmpCharB, BracketChars) then begin + BufCrd.Char := BufCrd.Char - 1; + S := TmpCharA; + end; + + if Editor.GetHighlighterAttriAtRowCol(BufCrd, S, Attri) and (Attri.FriendlyName = SYNS_FriendlyAttrSymbol) then + begin + + for i:=Low(OpenChars) to High(OpenChars) do begin + if (S = OpenChars[i]) or (S = CloseChars[i]) then begin + Pix := CharToPixels(BufCrd); + + Canvas.Brush.Style := bsSolid; + Canvas.Font.Assign(Editor.Font); + Canvas.Font.Style := Attri.Style; + + if (TransientType = ttAfter) then begin + Canvas.Font.Color := MatchingBraceForegroundColor; + Canvas.Brush.Color := MatchingBraceBackgroundColor; + end else begin + Canvas.Font.Color := Attri.Foreground; + Canvas.Brush.Color := Attri.Background; + end; + if Canvas.Font.Color = clNone then + Canvas.Font.Color := Editor.Font.Color; + if Canvas.Brush.Color = clNone then + Canvas.Brush.Color := Editor.Color; + + Canvas.TextOut(Pix.X, Pix.Y, S); + BufCrd := Editor.GetMatchingBracketEx(BufCrd); + + if (BufCrd.Char > 0) and (BufCrd.Line > 0) then begin + Pix := CharToPixels(BufCrd); + if Pix.X > Editor.Gutter.Width then begin + if S = OpenChars[i] then + Canvas.TextOut(Pix.X, Pix.Y, CloseChars[i]) + else + Canvas.TextOut(Pix.X, Pix.Y, OpenChars[i]); + end; + end; + + end; + end; + Canvas.Brush.Style := bsSolid; + end; + end; + end; + + // Release event handler + FSynEditInOnPaintTransient := False; +end;} + + +{procedure TMainForm.popupHostPopup(Sender: TObject); +begin + menuFetchDBitems.Enabled := (PageControlHost.ActivePage = tabDatabases) and (ListDatabases.SelectedCount > 0); + Kill1.Enabled := (PageControlHost.ActivePage = tabProcessList) and (ListProcesses.SelectedCount > 0); + menuEditVariable.Enabled := False; + if ActiveConnection.Has(frEditVariables) then + menuEditVariable.Enabled := (PageControlHost.ActivePage = tabVariables) and Assigned(ListVariables.FocusedNode) + else + menuEditVariable.Hint := _(SUnsupported); +end;} + + +{procedure TMainForm.popupDBPopup(Sender: TObject); +var + Obj: PDBObject; + HasFocus, IsDb, IsObject: Boolean; + Conn: TDBConnection; +begin + // DBtree and ListTables both use popupDB as menu + if PopupComponent(Sender) = DBtree then begin + Obj := DBTree.GetNodeData(DBTree.FocusedNode); + IsDb := Obj.NodeType = lntDb; + IsObject := Obj.NodeType in [lntTable..lntEvent]; + actCreateDatabase.Enabled := (Obj.NodeType = lntNone) + and (Obj.Connection.Parameters.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL]); + actConnectionProperties.Enabled := Obj.NodeType = lntNone; + actAttachDatabase.Visible := Obj.Connection.Parameters.IsAnySQLite; + actAttachDatabase.Enabled := actAttachDatabase.Visible and (Obj.NodeType = lntNone); + actCreateTable.Enabled := IsDb or IsObject or (Obj.GroupType = lntTable); + actCreateView.Enabled := IsDb or IsObject or (Obj.GroupType = lntView); + actCreateProcedure.Enabled := IsDb or IsObject or (Obj.GroupType in [lntFunction, lntProcedure]); + actCreateFunction.Enabled := actCreateProcedure.Enabled; + actCreateTrigger.Enabled := IsDb or IsObject or (Obj.GroupType = lntTrigger); + actCreateEvent.Enabled := IsDb or IsObject or (Obj.GroupType = lntEvent); + actDropObjects.Enabled := IsObject or + (IsDb and not Obj.Connection.Parameters.IsAnySQLite); + actDetachDatabase.Visible := Obj.Connection.Parameters.IsAnySQLite; + actDetachDatabase.Enabled := actDetachDatabase.Visible and (Obj.NodeType = lntDb); + actCopyTable.Enabled := Obj.NodeType in [lntTable, lntView]; + actEmptyTables.Enabled := Obj.NodeType in [lntTable, lntView]; + actRunRoutines.Enabled := Obj.NodeType in [lntProcedure, lntFunction]; + menuClearDataTabFilter.Enabled := Obj.NodeType in [lntTable, lntView]; + menuEditObject.Enabled := IsDb or IsObject; + // Enable certain items which are valid only here + menuTreeExpandAll.Enabled := True; + menuTreeCollapseAll.Enabled := True; + menuTreeOptions.Enabled := True; + end else begin + HasFocus := Assigned(ListTables.FocusedNode); + actCreateDatabase.Enabled := False; + actConnectionProperties.Enabled := False; + actAttachDatabase.Visible := False; + actCreateTable.Enabled := True; + actCreateView.Enabled := True; + actCreateProcedure.Enabled := True; + actCreateFunction.Enabled := True; + actCreateTrigger.Enabled := True; + actCreateEvent.Enabled := True; + actDropObjects.Enabled := ListTables.SelectedCount > 0; + actDetachDatabase.Visible := False; + actEmptyTables.Enabled := True; + actRunRoutines.Enabled := True; + menuClearDataTabFilter.Enabled := False; + menuEditObject.Enabled := HasFocus; + actCopyTable.Enabled := False; + if HasFocus then begin + Obj := ListTables.GetNodeData(ListTables.FocusedNode); + actCopyTable.Enabled := Obj.NodeType in [lntTable, lntView]; + end; + menuTreeExpandAll.Enabled := False; + menuTreeCollapseAll.Enabled := False; + menuTreeOptions.Enabled := False; + end; + Conn := ActiveConnection; + if (Conn <> nil) and (Conn.Parameters.IsAnyMySQL) then begin + actCreateView.Enabled := actCreateView.Enabled and Conn.Has(frCreateView); + actCreateProcedure.Enabled := actCreateProcedure.Enabled and Conn.Has(frCreateProcedure); + actCreateFunction.Enabled := actCreateFunction.Enabled and Conn.Has(frCreateFunction); + actCreateTrigger.Enabled := actCreateTrigger.Enabled and Conn.Has(frCreateTrigger); + actCreateEvent.Enabled := actCreateEvent.Enabled and Conn.Has(frCreateEvent); + end; +end;} + + +{procedure TMainForm.popupFilterPopup(Sender: TObject); +var + SQLFuncs: TSQLFunctionList; + i, j: Integer; + miGroup, miFunction: TMenuItem; +begin + // Create function menu items in popup menu + menuFilterInsertFunction.Clear; + SQLFuncs := ActiveConnection.SQLFunctions; + for i:=0 to SQLFuncs.Categories.Count-1 do begin + // Create a menu item which gets subitems later + miGroup := TMenuItem.Create(popupFilter); + miGroup.Caption := SQLFuncs.Categories[i]; + menuFilterInsertFunction.Add(miGroup); + for j:=0 to SQLFuncs.Count-1 do begin + if SQLFuncs[j].Category <> SQLFuncs.Categories[i] then + Continue; + miFunction := TMenuItem.Create(popupFilter); + miFunction.Caption := SQLFuncs[j].Name; + miFunction.ImageIndex := 13; + // Prevent generating a hotkey + miFunction.Caption := StringReplace(miFunction.Caption, '&', '&&', [rfReplaceAll]); + // Prevent generating a seperator line + if miFunction.Caption = '-' then + miFunction.Caption := '&-'; + miFunction.Hint := SQLFuncs[j].Name + SQLFuncs[j].Declaration + ' - ' + StrEllipsis(SQLFuncs[j].Description, 200); + // Prevent generating a seperator for ShortHint and LongHint + miFunction.Hint := StringReplace( miFunction.Hint, '|', '¦', [rfReplaceAll] ); + miFunction.Tag := j; + // Place menuitem on menu + miFunction.OnClick := insertFunction; + miGroup.Add(miFunction); + end; + end; +end;} + + +{procedure TMainForm.popupDataGridPopup(Sender: TObject); +var + Grid: TVirtualStringTree; + Results: TDBQuery; + i: Integer; + Col, Value, FocusedColumnName: String; + CellFocused, InDataGrid, HasNullValue, HasNotNullValue: Boolean; + RowNumber: PInt64; + Node: PVirtualNode; + OldDataLocalNumberFormat: Boolean; + IncludedValues: TStringList; + Act: TAction; + Datatype: TDBDatatype; + ForeignKey: TForeignKey; +const + CLPBRD : String = 'CLIPBOARD'; +begin + // Manipulate quick filter menuitems + Grid := ActiveGrid; + // Make sure ValidateControls detects the grid as focused, which is not the case when + // it has 0 nodes, even with TreeOptions.SelectionOptions.RightclickSelect enabled + Grid.SetFocus; + CellFocused := Assigned(Grid.FocusedNode) and (Grid.FocusedColumn > 0); + InDataGrid := Grid = DataGrid; + DataInsertValue.Enabled := CellFocused; + QFvalues.Enabled := CellFocused; + menuQuickFilter.Enabled := InDataGrid; + actDataResetSorting.Enabled := InDataGrid; + menuSQLHelpData.Enabled := InDataGrid; + Refresh3.Enabled := InDataGrid; + actGridEditFunction.Enabled := CellFocused; + + if not CellFocused then + Exit; + Results := GridResult(Grid); + + Datatype := Results.DataType(Grid.FocusedColumn-1); + Col := Results.Connection.QuoteIdent(Results.ColumnOrgNames[Grid.FocusedColumn-1], False); + if InDataGrid + and (Datatype.Index = dbdtJson) + and Results.Connection.Parameters.IsAnyPostgreSQL then begin + Col := Col + '::text'; + end; + + // Block 1: WHERE col IN ([focused cell values]) + actQuickFilterFocused1.Hint := ''; + actQuickFilterFocused2.Hint := ''; + actQuickFilterFocused3.Hint := ''; + actQuickFilterFocused4.Hint := ''; + actQuickFilterFocused5.Hint := ''; + actQuickFilterFocused6.Hint := ''; + actQuickFilterFocused7.Hint := ''; + Node := Grid.GetFirstSelected; + HasNullValue := False; + HasNotNullValue := False; + OldDataLocalNumberFormat := DataLocalNumberFormat; + DataLocalNumberFormat := False; + IncludedValues := TStringList.Create; + while Assigned(Node) do begin + AnyGridEnsureFullRow(Grid, Node); + RowNumber := Grid.GetNodeData(Node); + Results.RecNo := RowNumber^; + if Results.IsNull(Grid.FocusedColumn-1) then + HasNullValue := True + else begin + HasNotNullValue := True; + Value := Grid.Text[Node, Grid.FocusedColumn]; + if IncludedValues.IndexOf(Value) = -1 then begin + actQuickFilterFocused1.Hint := actQuickFilterFocused1.Hint + Results.Connection.EscapeString(Value, Datatype) + ', '; + actQuickFilterFocused2.Hint := actQuickFilterFocused2.Hint + Results.Connection.EscapeString(Value, Datatype) + ', '; + actQuickFilterFocused3.Hint := actQuickFilterFocused3.Hint + + Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''' + Results.Connection.EscapeString(Value, True, False) + '%''']) + + ' OR '; + actQuickFilterFocused4.Hint := actQuickFilterFocused4.Hint + + Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '''']) + + ' OR '; + actQuickFilterFocused5.Hint := actQuickFilterFocused5.Hint + + Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '%''']) + + ' OR '; + actQuickFilterFocused6.Hint := actQuickFilterFocused6.Hint + Col + ' > ' + Results.Connection.EscapeString(Value, Datatype) + ' OR '; + actQuickFilterFocused7.Hint := actQuickFilterFocused7.Hint + Col + ' < ' + Results.Connection.EscapeString(Value, Datatype) + ' OR '; + IncludedValues.Add(Value); + end; + end; + Node := Grid.GetNextSelected(Node); + if Length(actQuickFilterFocused1.Hint) > SIZE_MB then + Break; + end; + DataLocalNumberFormat := OldDataLocalNumberFormat; + if HasNotNullValue then begin + actQuickFilterFocused1.Hint := Col + ' IN (' + Copy(actQuickFilterFocused1.Hint, 1, Length(actQuickFilterFocused1.Hint)-2) + ')'; + actQuickFilterFocused2.Hint := Col + ' NOT IN (' + Copy(actQuickFilterFocused2.Hint, 1, Length(actQuickFilterFocused2.Hint)-2) + ')'; + actQuickFilterFocused3.Hint := Copy(actQuickFilterFocused3.Hint, 1, Length(actQuickFilterFocused3.Hint)-4); + actQuickFilterFocused4.Hint := Copy(actQuickFilterFocused4.Hint, 1, Length(actQuickFilterFocused4.Hint)-4); + actQuickFilterFocused5.Hint := Copy(actQuickFilterFocused5.Hint, 1, Length(actQuickFilterFocused5.Hint)-4); + actQuickFilterFocused6.Hint := Copy(actQuickFilterFocused6.Hint, 1, Length(actQuickFilterFocused6.Hint)-4); + actQuickFilterFocused7.Hint := Copy(actQuickFilterFocused7.Hint, 1, Length(actQuickFilterFocused7.Hint)-4); + end; + if HasNullValue then begin + if HasNotNullValue then begin + actQuickFilterFocused1.Hint := actQuickFilterFocused1.Hint + ' OR '; + actQuickFilterFocused2.Hint := actQuickFilterFocused2.Hint + ' AND '; + actQuickFilterFocused3.Hint := actQuickFilterFocused3.Hint + ' OR '; + actQuickFilterFocused4.Hint := actQuickFilterFocused4.Hint + ' OR '; + actQuickFilterFocused5.Hint := actQuickFilterFocused5.Hint + ' OR '; + actQuickFilterFocused6.Hint := actQuickFilterFocused6.Hint + ' OR '; + actQuickFilterFocused7.Hint := actQuickFilterFocused7.Hint + ' OR '; + end; + actQuickFilterFocused1.Hint := actQuickFilterFocused1.Hint + Col + ' IS NULL'; + actQuickFilterFocused2.Hint := actQuickFilterFocused2.Hint + Col + ' IS NOT NULL'; + actQuickFilterFocused3.Hint := actQuickFilterFocused3.Hint + Col + ' IS NULL'; + actQuickFilterFocused4.Hint := actQuickFilterFocused4.Hint + Col + ' IS NULL'; + actQuickFilterFocused5.Hint := actQuickFilterFocused5.Hint + Col + ' IS NULL'; + actQuickFilterFocused6.Hint := actQuickFilterFocused6.Hint + Col + ' IS NULL'; + actQuickFilterFocused7.Hint := actQuickFilterFocused7.Hint + Col + ' IS NULL'; + end; + actQuickFilterFocused1.Visible := HasNotNullValue or HasNullValue; + actQuickFilterFocused2.Visible := HasNotNullValue or HasNullValue; + actQuickFilterFocused3.Visible := HasNotNullValue; + actQuickFilterFocused4.Visible := HasNotNullValue; + actQuickFilterFocused5.Visible := HasNotNullValue; + actQuickFilterFocused6.Visible := HasNotNullValue; + actQuickFilterFocused7.Visible := HasNotNullValue; + IncludedValues.Free; + + // Block 2: WHERE col = [ask user for value] + actQuickFilterPrompt1.Hint := Col + ' = "..."'; + actQuickFilterPrompt2.Hint := Col + ' != "..."'; + actQuickFilterPrompt3.Hint := Col + ' > "..."'; + actQuickFilterPrompt4.Hint := Col + ' < "..."'; + actQuickFilterPrompt5.Hint := Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '"%...%"']); + actQuickFilterNull.Hint := Col + ' IS NULL'; + actQuickFilterNotNull.Hint := Col + ' IS NOT NULL'; + + // Block 3: WHERE col = [clipboard content] + Value := Trim(Clipboard.TryAsText); + if Length(Value) < SIZE_KB then begin + actQuickFilterClipboard1.Enabled := true; + actQuickFilterClipboard1.Hint := Col + ' = ' + Results.Connection.EscapeString(Value, Datatype); + actQuickFilterClipboard2.Enabled := true; + actQuickFilterClipboard2.Hint := Col + ' != ' + Results.Connection.EscapeString(Value, Datatype); + actQuickFilterClipboard3.Enabled := true; + actQuickFilterClipboard3.Hint := Col + ' > ' + Results.Connection.EscapeString(Value, Datatype); + actQuickFilterClipboard4.Enabled := true; + actQuickFilterClipboard4.Hint := Col + ' < ' + Results.Connection.EscapeString(Value, Datatype); + actQuickFilterClipboard5.Enabled := true; + actQuickFilterClipboard5.Hint := Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '''%' + Results.Connection.EscapeString(Value, True, False) + '%''']); + actQuickFilterClipboard6.Enabled := true; + actQuickFilterClipboard6.Hint := Col + ' IN (' + Value + ')'; + end else begin + actQuickFilterClipboard1.Enabled := false; + actQuickFilterClipboard1.Hint := Col + ' = ' + CLPBRD; + actQuickFilterClipboard2.Enabled := false; + actQuickFilterClipboard2.Hint := Col + ' != ' + CLPBRD; + actQuickFilterClipboard3.Enabled := false; + actQuickFilterClipboard3.Hint := Col + ' > ' + CLPBRD; + actQuickFilterClipboard4.Enabled := false; + actQuickFilterClipboard4.Hint := Col + ' < ' + CLPBRD; + actQuickFilterClipboard5.Enabled := false; + actQuickFilterClipboard5.Hint := Results.Connection.GetSQLSpecifity(spLikeCompare, [Col, '%' + CLPBRD + '%']); + actQuickFilterClipboard6.Enabled := false; + actQuickFilterClipboard6.Hint := Col + ' IN (' + CLPBRD + ')'; + end; + + // Set captions from hints + for i:=0 to menuQuickFilter.Count-1 do begin + if menuQuickFilter[i].Action = nil then + Continue; + Act := menuQuickFilter[i].Action as TAction; + // Stop here + if Act = actRemoveFilter then + Break; + if not Act.Hint.IsEmpty then + Act.Caption := StrEllipsis(Act.Hint, 100); + end; + + actFollowForeignKey.Enabled := False; + if (InDataGrid) then begin + FocusedColumnName := Results.ColumnOrgNames[Grid.FocusedColumn-1]; + //find foreign key for current column + for ForeignKey in ActiveDBObj.TableForeignKeys do begin + i := ForeignKey.Columns.IndexOf(FocusedColumnName); + if i > -1 then begin + actFollowForeignKey.Enabled := True; + break; + end; + end; + end; +end;} + + +{procedure TMainForm.QFvaluesClick(Sender: TObject); +var + Data: TDBQuery; + DbObj: TDBObject; + Conn: TDBConnection; + ColIdx, ResultCol: Integer; + ColName, Query: String; + ColType: TDBDatatype; + TableCol: TTableColumn; + Item: TMenuItem; + i: Integer; + MaxSize: Int64; + ValueList: TStringList; + ColumnHasIndex: Boolean; +begin + // Create a list of distinct column values in selected table + for i:=QFvalues.Count-1 downto 1 do + QFvalues.Delete(i); + QFvalues[0].Caption := ''; + QFvalues[0].Hint := ''; + QFvalues[0].OnClick := nil; + ColIdx := DataGrid.FocusedColumn; + ResultCol := ColIdx - 1; + if ColIdx = NoColumn then + Exit; + ColName := DataGridResult.ColumnOrgNames[ResultCol]; + ColType := DataGridResult.DataType(ResultCol); + ShowStatusMsg(_('Fetching distinct values ...')); + DbObj := ActiveDbObj; + Conn := DbObj.Connection; + MaxSize := SIZE_GB; + ColumnHasIndex := DataGridResult.ColIsKeyPart(ResultCol) + or DataGridResult.ColIsUniqueKeyPart(ResultCol) + or DataGridResult.ColIsPrimaryKeyPart(ResultCol); + if ColumnHasIndex then begin + MaxSize := MaxSize * 5; + end; + try + if DbObj.Size > MaxSize then + raise Exception.Create(f_('Table too large (>%s), avoiding long running SELECT query', [FormatByteNumber(MaxSize)])); + Query := Conn.QuoteIdent(ColName)+', COUNT(*) AS c FROM '+DbObj.QuotedName; + if SynMemoFilter.Text <> '' then + Query := Query + ' WHERE ' + SynMemoFilter.Text + CRLF; + Query := Query + ' GROUP BY '+Conn.QuoteIdent(ColName)+' ORDER BY c DESC, '+Conn.QuoteIdent(ColName); + Data := Conn.GetResults(Conn.ApplyLimitClause('SELECT', Query, 30, 0)); + for i:=0 to Data.RecordCount-1 do begin + if QFvalues.Count > i then + Item := QFvalues[i] + else begin + Item := TMenuItem.Create(QFvalues); + QFvalues.Add(Item); + end; + if Data.IsNull(ColName) then + Item.Hint := Conn.QuoteIdent(ColName)+' IS NULL' + else if ColType.Category in [dtcBinary, dtcSpatial] then + Item.Hint := Conn.QuoteIdent(ColName)+'='+Data.HexValue(0, False) + else + Item.Hint := Conn.QuoteIdent(ColName)+'='+Conn.EscapeString(Data.Col(ColName)); + Item.Caption := StrEllipsis(Item.Hint, 100) + ' (' + FormatNumber(Data.Col('c')) + ')'; + if SynMemoFilter.Text <> '' then begin + if Pos(Item.Hint, SynMemoFilter.Text) > 0 then + Item.Hint := SynMemoFilter.Text + else + Item.Hint := SynMemoFilter.Text + ' AND ' + Item.Hint; + end; + Item.OnClick := QuickFilterClick; + Data.Next; + end; + except + on E:Exception do begin + // Table is too large for the above SELECT query, or the query failed, due to an error in its filter or so + // Get ENUM/SET values instead if possible + QFvalues[0].Caption := StrEllipsis(E.Message, 100); + QFvalues[0].Hint := E.Message; + for TableCol in SelectedTableColumns do begin + if (TableCol.Name = ColName) and (TableCol.DataType.Index in [dbdtEnum, dbdtSet]) then begin + ValueList := TableCol.ValueList; + for i:=0 to ValueList.Count-1 do begin + if QFvalues.Count > i+1 then + Item := QFvalues[i+1] + else begin + Item := TMenuItem.Create(QFvalues); + QFvalues.Add(Item); + end; + Item.Hint := Conn.QuoteIdent(ColName)+'='+Conn.EscapeString(ValueList[i]); + Item.Caption := StrEllipsis(Item.Hint, 100); + Item.OnClick := QuickFilterClick; + end; + Break; + end; + end; + end; + end; + ShowStatusMsg; +end;} + + +{procedure TMainForm.DataInsertValueClick(Sender: TObject); +var + LocalTime, UtcTime: TDateTime; + y, m, d, h, i, s, ms: Word; + Uid: TGuid; + DateTimeSQL, StrUid: String; + UnixTimestamp, UtcUnixTimestamp: Int64; + SystemTime: TSystemTime; + ColNum: TColumnIndex; + Col: TTableColumn; + Conn: TDBConnection; +const + FrmDateTime = '%s: %.4d-%.2d-%.2d %.2d:%.2d:%.2d'; + FrmDate = '%s: %.4d-%.2d-%.2d'; + FrmTime = '%s: %.2d:%.2d:%.2d'; + FrmYear = '%s: %.4d'; + FrmUnixTs = '%s: %d'; +begin + // Local and UTC date/time menu items + Conn := ActiveConnection; + DateTimeSQL := 'SELECT ' + Conn.GetSQLSpecifity(spFuncNow); + LocalTime := Conn.ParseDateTime(Conn.GetVar(DateTimeSQL)); + DecodeDateTime(LocalTime, y, m, d, h, i, s, ms); + DataDateTime.Caption := Format(FrmDateTime, [_('Date and time'), y,m,d,h,i,s]); + DataDate.Caption := Format(FrmDate, [_('Date'), y,m,d]); + DataTime.Caption := Format(FrmTime, [_('Time'), h,i,s]); + DataYear.Caption := Format(FrmYear, [_('Year'), y]); + GetSystemTime(SystemTime); + UnixTimestamp := DateTimeToUnix(SystemTimeToDateTime(SystemTime)); + DataUnixTimestamp.Caption := Format(FrmUnixTs, [_('UNIX Timestamp'), UnixTimestamp]); + + UtcTime := IncSecond(LocalTime, FTimeZoneOffset); + DecodeDateTime(UtcTime, y, m, d, h, i, s, ms); + DataUtcDateTime.Caption := Format(FrmDateTime, ['UTC '+_('Date and time'), y,m,d,h,i,s]); + DataUtcDate.Caption := Format(FrmDate, ['UTC '+_('Date'), y,m,d]); + DataUtcTime.Caption := Format(FrmTime, ['UTC '+_('Time'), h,i,s]); + UtcUnixTimestamp := UnixTimestamp + FTimeZoneOffset; + DataUtcUnixTimestamp.Caption := Format(FrmUnixTs, ['UTC '+_('UNIX Timestamp'), UtcUnixTimestamp]); + + CreateGuid(Uid); + StrUid := GuidToString(Uid); + DataGUID.Caption := _('GUID') + ': ' + StrUid; + DataGUIDwobraces.Caption := _('GUID without braces') + ': ' + Copy(StrUid, 2, Length(StrUid)-2); + DataGUIDlowercase.Caption := _('GUID lowercase') + ': ' + StrUid.ToLower; + DataGUIDlowercaseWobraces.Caption := _('GUID lowercase without braces') + ': ' + Copy(StrUid, 2, Length(StrUid)-2).ToLower; + + ColNum := DataGrid.FocusedColumn; + DataDefaultValue.Caption := _('Default value')+': ?'; + DataDefaultValue.Enabled := False; + if ColNum <> NOCOLUMN then begin + for Col in SelectedTableColumns do begin + if (Col.Name = DataGrid.Header.Columns[ColNum].Text) and (Col.DefaultType = cdtText) then begin + DataDefaultValue.Caption := _('Default value')+': '+Col.DefaultText; + DataDefaultValue.Enabled := True; + break; + end; + end; + end; +end;} + + +{procedure TMainForm.InsertValue(Sender: TObject); +var + d: String; + p: Integer; + Grid: TVirtualStringTree; +begin + // Insert date/time-value into table + d := StripHotkey((Sender as TMenuItem).Caption); + p := Pos(':', d); + if p > 0 then + d := Trim(Copy(d, p+1, MaxInt)); + Grid := ActiveGrid; + try + Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := d; + except on E:EDbError do + ErrorDialog(E.Message); + end; +end;} + + +{function TMainForm.GetRootNode(Tree: TBaseVirtualTree; Connection: TDBConnection): PVirtualNode; +var + SessionNode: PVirtualNode; + SessionObj: PDBObject; +begin + Result := nil; + SessionNode := Tree.GetFirstChild(nil); + while Assigned(SessionNode) do begin + SessionObj := Tree.GetNodeData(SessionNode); + if SessionObj.Connection = Connection then begin + Result := SessionNode; + break; + end; + SessionNode := Tree.GetNextSibling(SessionNode); + end; +end;} + + +{function TMainForm.GetActiveConnection: TDBConnection; +begin + Result := nil; + if Assigned(FActiveDBObj) and (FActiveDbObj <> nil) then + Result := FActiveDbObj.Connection; +end;} + + +{function TMainForm.GetActiveDatabase: String; +begin + // Find currently selected database in active connection + Result := ''; + if (not (csDestroying in ComponentState)) + and Assigned(FActiveDBObj) + and Assigned(FActiveDBObj.Connection) then + Result := FActiveDBObj.Connection.Database; +end;} + + +{procedure TMainForm.SetActiveDatabase(db: String; Connection: TDBConnection); +var + SessionNode, DBNode: PVirtualNode; + DBObj: PDBObject; +begin + // Set focus on the wanted db node + LogSQL('SetActiveDatabase('+db+')', lcDebug); + SessionNode := GetRootNode(DBtree, Connection); + if db = '' then + SelectNode(DBtree, SessionNode) + else begin + DBNode := DBtree.GetFirstChild(SessionNode); + while Assigned(DBNode) do begin + DBObj := DBtree.GetNodeData(DBNode); + if DBObj.Database = db then begin + if DBNode <> DBtree.FocusedNode then + SelectNode(DBtree, DBNode); + break; + end; + DBNode := DBtree.GetNextSibling(DBNode); + end; + end; +end;} + + +{procedure TMainForm.SetActiveDBObj(Obj: TDBObject); +var + FoundNode: PVirtualNode; +begin + // Find right table/view/... node in tree and select it, implicitely call OnFocusChanged + LogSQL('SetActiveDBObj('+Obj.Name+')', lcDebug); + FoundNode := FindDBObjectNode(DBtree, Obj); + if Assigned(FoundNode) then + SelectNode(DBTree, FoundNode) + else + LogSQL(f_('Table node "%s" not found in tree.', [Obj.Name]), lcError); +end;} + + +{function TMainForm.FindDBObjectNode(Tree: TBaseVirtualTree; Obj: TDBObject): PVirtualNode; +var + DbNode, ObjectNode, GroupedNode: PVirtualNode; + DbObj, ObjectObj, GroupedObj: PDBObject; +begin + Result := nil; + DbNode := Tree.GetFirstChild(GetRootNode(Tree, Obj.Connection)); + while Assigned(DbNode) do begin + // Search in database nodes + DbObj := Tree.GetNodeData(DbNode); + if DBObj.IsSameAs(Obj) then begin + Result := DBNode; + break; + end; + + // Search in table/view/... nodes + if DbObj.Database = Obj.Database then begin + ObjectNode := Tree.GetFirstChild(DbNode); + while Assigned(ObjectNode) do begin + ObjectObj := Tree.GetNodeData(ObjectNode); + if ObjectObj.IsSameAs(Obj) then begin + Result := ObjectNode; + break; + end; + + // Search in grouped table/view/... nodes + GroupedNode := Tree.GetFirstChild(ObjectNode); + while Assigned(GroupedNode) do begin + GroupedObj := Tree.GetNodeData(GroupedNode); + if GroupedObj.IsSameAs(Obj) then begin + Result := GroupedNode; + break; + end; + GroupedNode := Tree.GetNextSibling(GroupedNode); + end; + + ObjectNode := Tree.GetNextSibling(ObjectNode); + end; + break; + end; + DbNode := Tree.GetNextSibling(DbNode); + end; +end;} + + +{** + Column selection for datagrid +} +{procedure TMainForm.btnDataClick(Sender: TObject); +var + btn : TToolButton; + frm : TForm; +begin + btn := (Sender as TToolButton); + + if (btn = tbtnDataColumns) or (btn = tbtnDataSorting) then begin + // Create desired form for SELECT and ORDER buttons + btn.Down := not btn.Down; + if not btn.Down then Exit; + if btn = tbtnDataColumns then + frm := TfrmColumnSelection.Create(self) + else if btn = tbtnDataSorting then + frm := TfrmDataSorting.Create(self) + else + frm := TForm.Create(self); // Dummy fallback, should never get created + // Position new form relative to btn's position + frm.Top := btn.ClientOrigin.Y + btn.Height; + frm.Left := btn.ClientOrigin.X + btn.Width - frm.Width; + // Display form + frm.Show; + end else if btn = tbtnDataFilter then begin + // Unhide inline filter panel + ToggleFilterPanel; + FilterPanelManuallyOpened := pnlFilter.Visible; + if FilterPanelManuallyOpened then + SynMemoFilter.SetFocus; + end; +end;} + + +{procedure TMainForm.filterQueryHelpersChange(Sender: TObject); +begin + // Filter nodes in query helpers + FilterNodesByEdit(Sender as TButtonedEdit, QueryTabs.ActiveHelpersTree); +end;} + + +{procedure TMainForm.tabsetQueryMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); +var + idx, i: Integer; + Tabs: TTabSet; + Rect: TRect; + Org: TPoint; + QueryTab: TQueryTab; + ResultTab: TResultTab; + HintSQL: TStringList; +begin + // Display some hint with row/col count + SQL when mouse hovers over result tab + if (FLastHintMousepos.X = x) and (FLastHintMousepos.Y = Y) then + Exit; + FLastHintMousepos := Point(X, Y); + Tabs := Sender as TTabSet; + idx := Tabs.ItemAtPos(Point(X, Y), True); + if (idx = -1) or (idx = FLastHintControlIndex) then + Exit; + FLastHintControlIndex := idx; + // Check if user wants these balloon hints + if not AppSettings.ReadBool(asHintsOnResultTabs) then + Exit; + QueryTab := QueryTabs.ActiveTab; + if idx >= QueryTab.ResultTabs.Count then + Exit; + + // Make SQL readable for the tooltip balloon. WrapText() is unsuitable here. + // See issue #2014 + // Also, wee need to work around the awful looking balloon text: + // http://qc.embarcadero.com/wc/qcmain.aspx?d=73771 + ResultTab := QueryTab.ResultTabs[idx]; + HintSQL := TStringList.Create; + HintSQL.Text := Trim(ResultTab.Results.SQL); + for i:=0 to HintSQL.Count-1 do begin + HintSQL[i] := StrEllipsis(HintSQL[i], 100); + HintSQL[i] := StringReplace(HintSQL[i], #9, ' ', [rfReplaceAll]); + end; + BalloonHint1.Description := FormatNumber(ResultTab.Results.ColumnCount) + ' columns × ' + + FormatNumber(ResultTab.Results.RecordCount) + ' rows' + CRLF + CRLF + + Trim(StrEllipsis(HintSQL.Text, SIZE_KB)); + Rect := Tabs.ItemRect(idx); + Org := Tabs.ClientOrigin; + OffsetRect(Rect, Org.X, Org.Y); + BalloonHint1.ShowHint(Rect); +end;} + + +{procedure TMainForm.tabsetQueryMouseLeave(Sender: TObject); +begin + // BalloonHint.HideAfter is -1, so it will stay forever if we wouldn't hide it at some point + BalloonHint1.HideHint; + FLastHintControlIndex := -1; +end;} + + +{** + Insert function name from popupmenu to query memo +} +{procedure TMainForm.insertFunction(Sender: TObject); +var + f : String; + sm : TSynMemo; + Conn: TDBConnection; +begin + // Detect which memo is focused + if SynMemoFilter.Focused then + sm := SynMemoFilter + else + sm := QueryTabs.ActiveMemo; + // Restore function name from tag + Conn := ActiveConnection; + f := Conn.SQLFunctions[TControl(Sender).tag].Name + + Conn.SQLFunctions[TControl(Sender).tag].Declaration; + sm.UndoList.AddGroupBreak; + sm.SelText := f; + sm.UndoList.AddGroupBreak; + if not SynMemoFilter.Focused then + ValidateQueryControls(Sender); +end;} + + +{** + Delete a snippet file +} +{procedure TMainForm.menuDeleteSnippetClick(Sender: TObject); +var + snippetfile : String; +begin + // Don't do anything if no item was selected + if not Assigned(QueryTabs.ActiveHelpersTree.FocusedNode) then + Exit; + + snippetfile := AppSettings.DirnameSnippets + QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0] + '.sql'; + if MessageDialog(_('Delete snippet file?'), snippetfile, mtConfirmation, [mbOk, mbCancel]) = mrOk then + begin + Screen.Cursor := crHourGlass; + if DeleteFileWithUndo(snippetfile) then begin + // Refresh list with snippets + SetSnippetFilenames; + end else begin + Screen.Cursor := crDefault; + ErrorDialog(f_('Failed deleting %s', [snippetfile])); + end; + Screen.Cursor := crDefault; + end; +end;} + + +{procedure TMainForm.menuDoubleClickInsertsNodeTextClick(Sender: TObject); +var + Item: TMenuItem; +begin + // Activate doubleclick node feature + Item := Sender as TMenuItem; + Item.Checked := not Item.Checked; + AppSettings.ResetPath; + AppSettings.WriteBool(asDoubleClickInsertsNodeText, Item.Checked); +end;} + +{** + Load snippet at cursor +} +{procedure TMainForm.menuInsertAtCursorClick(Sender: TObject); +var + Tree: TVirtualStringTree; + Tab: TQueryTab; +begin + Tree := QueryTabs.ActiveHelpersTree; + Tab := QueryTabs.ActiveTab; + Tab.Memo.DragDrop(Tree, Tab.Memo.CaretX, Tab.Memo.CaretY); +end;} + + +{** + Load snippet and replace content +} +{procedure TMainForm.menuLoadSnippetClick(Sender: TObject); +begin + QueryTabs.ActiveTab.LoadContents(AppSettings.DirnameSnippets + QueryTabs.ActiveHelpersTree.Text[QueryTabs.ActiveHelpersTree.FocusedNode, 0] + '.sql', True, nil); +end;} + + +{** + Open snippets-directory in Explorer +} +{procedure TMainForm.menuExploreClick(Sender: TObject); +begin + ShellExec('', AppSettings.DirnameSnippets); +end;} + + +{procedure TMainForm.menuClearQueryHistoryClick(Sender: TObject); +var + Values: TStringList; + PathToDelete: String; +begin + // Clear query history items in registry + // Take care of MessageDialog, probably changing the current SessionPath + PathToDelete := ActiveConnection.Parameters.SessionPath + '\' + REGKEY_QUERYHISTORY; + AppSettings.SessionPath := PathToDelete; + Values := AppSettings.GetValueNames; + if MessageDialog(_('Clear query history?'), f_('%s history items will be deleted.', [FormatNumber(Values.Count)]), mtConfirmation, [mbYes, mbNo]) = mrYes then begin + Screen.Cursor := crHourglass; + AppSettings.SessionPath := PathToDelete; + AppSettings.DeleteCurrentKey; + RefreshHelperNode(TQueryTab.HelperNodeHistory); + Screen.Cursor := crDefault; + end; + Values.Free; + AppSettings.ResetPath; +end;} + + +{procedure TMainForm.menuFetchDBitemsClick(Sender: TObject); +var + Node: PVirtualNode; + db: String; + Conn: TDBConnection; +begin + // Fill db object cache of selected databases + try + Screen.Cursor := crHourglass; + Node := GetNextNode(ListDatabases, nil, True); + Conn := ActiveConnection; + while Assigned(Node) do begin + db := ListDatabases.Text[Node, 0]; + Conn.GetDBObjects(db, True); + ListDatabases.RepaintNode(Node); + DBtree.RepaintNode(FindDBNode(DBtree, Conn, db)); + Node := GetNextNode(ListDatabases, Node, True); + end; + finally + Screen.Cursor := crDefault; + end; +end;} + + +{** + A column header of a VirtualStringTree was clicked: + Toggle the sort direction +} +{procedure TMainForm.AnyGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); +var + ConfirmResult: Integer; + MsgStr: String; + LongSortRowNum: Cardinal; +begin + // Don't call sorting procedure on right click + // Some list-headers have a contextmenu which should popup then. + if HitInfo.Button = mbRight then + Exit; + // Beginning with VT's r181, this proc is also called when doubleclicking-to-autofit + // Seems buggy in VT as this suddenly calls it with Column=-1 in those cases. + // See also issue #1150 + if HitInfo.Column = NoColumn then + Exit; + if Sender.Columns[HitInfo.Column].CheckBox then + Exit; + // Header click disabled + if not AppSettings.ReadBool(asColumnHeaderClick) then + Exit; + // Large query result sorting takes too long, see #293 + LongSortRowNum := AppSettings.ReadInt(asQueryGridLongSortRowNum); + if TVirtualStringTree(Sender.Treeview).RootNodeCount > LongSortRowNum then begin + MsgStr := f_('Sort operation on grid containing more than %d rows can take longer. Do you want to wait for it?', [LongSortRowNum]); + ConfirmResult := MessageDialog('Wait longer for sorting?', MsgStr, mtConfirmation, [mbYes, mbNo]); + if ConfirmResult <> mrYes then + Exit; + end; + + if (Sender.SortColumn <> HitInfo.Column) or (Sender.SortDirection = sdDescending) then begin + Sender.SortDirection := sdAscending; + end else if Sender.SortDirection = sdAscending then begin + Sender.SortDirection := sdDescending; + end; + Screen.Cursor := crHourglass; + Sender.SortColumn := HitInfo.Column; + TBaseVirtualTree(Sender.Treeview).SortTree( HitInfo.Column, Sender.SortDirection ); + Screen.Cursor := crDefault; +end;} + + +{** + Sorting a column of a VirtualTree by comparing two cells +} +{procedure TMainForm.AnyGridCompareNodes(Sender: TBaseVirtualTree; Node1, + Node2: PVirtualNode; Column: TColumnIndex; var Result: Integer); +var + VT: TVirtualStringTree; +begin + VT := Sender as TVirtualStringTree; + if Assigned(Node1) and Assigned(Node2) then + Result := CompareAnyNode(VT.Text[Node1, Column], VT.Text[Node2, Column]); +end;} + + +{** + VirtualTree was painted. Adjust background color of sorted column. +} +{procedure TMainForm.AnyGridAfterPaint(Sender: TBaseVirtualTree; + TargetCanvas: TCanvas); +var + i: Integer; + h: TVTHeader; + NewColor: TColor; +begin + h := (Sender as TVirtualStringTree).Header; + for i:=0 to h.Columns.Count-1 do begin + NewColor := GetThemeColor(clWindow); + if h.SortColumn = i then + NewColor := ColorAdjustBrightness(NewColor, COLORSHIFT_SORTCOLUMNS); + h.Columns[i].Color := NewColor; + end; +end;} + + +{** + Start writing logfile. + Called either in FormShow or after closing preferences dialog +} +{procedure TMainForm.SetLogToFile(Value: Boolean); +var + LogfilePattern, LogDir: String; + i : Integer; +begin + if Value = FLogToFile then + Exit; + + if Value then begin + // Ensure directory exists + LogDir := AppSettings.ReadString(asSessionLogsDirectory); + LogDir := IncludeTrailingPathDelimiter(LogDir); + ForceDirectories(LogDir); + + // Determine free filename + LogfilePattern := '%.6u.log'; + i := 1; + FFileNameSessionLog := LogDir + ValidFilename(Format(LogfilePattern, [i])); + while FileExists(FFileNameSessionLog) do begin + inc(i); + FFileNameSessionLog := LogDir + ValidFilename(Format(LogfilePattern, [i])); + end; + + // Create file handle for writing + AssignFile( FFileHandleSessionLog, FFileNameSessionLog );} + //{$I-} // Supress errors + //if FileExists(FFileNameSessionLog) then + // Append(FFileHandleSessionLog) + //else + // Rewrite(FFileHandleSessionLog); + //{$I+} + {if IOResult <> 0 then begin + AppSettings.WriteBool(asLogToFile, False); + ErrorDialog(_('Error opening session log file'), FFileNameSessionLog+CRLF+CRLF+_('Logging is disabled now.')); + end else begin + FLogToFile := Value; + LogSQL(f_('Writing to session log file now: %s', [FFileNameSessionLog])); + end; + end else begin} + //{$I-} // Supress errors + //CloseFile(FFileHandleSessionLog); + //{$I+} + {// Reset IOResult so later checks in ActivateFileLogging doesn't get an old value + IOResult; + FLogToFile := Value; + LogSQL(_('Writing to session log file disabled now')); + end; +end;} + + +{** + Display tooltips in VirtualTrees. Imitates default behaviour of TListView. +} +{procedure TMainForm.AnyGridGetHint(Sender: TBaseVirtualTree; Node: + PVirtualNode; Column: TColumnIndex; var LineBreakStyle: + TVTTooltipLineBreakStyle; var HintText: String); +var + Tree: TVirtualStringTree; + NewHint: String; + Conn: TDBConnection; + ValIsNumber: Boolean; +begin + // Disable tooltips on Wine, as they prevent users from clicking + editing clipped cells + if IsWine then + Exit; + + Tree := TVirtualStringTree(Sender); + + if Tree = QueryTabs.ActiveHelpersTree then begin + Conn := ActiveConnection; + case Sender.GetNodeLevel(Node) of + 1: case Node.Parent.Index of + TQueryTab.HelperNodeFunctions: begin + NewHint := Conn.SQLFunctions[Node.Index].Name + Conn.SQLFunctions[Node.Index].Declaration + + ':' + sLineBreak + Conn.SQLFunctions[Node.Index].Description; + if not NewHint.IsEmpty then begin + HintText := NewHint; + end; + end; + end; + end; + end; + + if HintText.IsEmpty then begin + try + ValIsNumber := IntToStr(MakeInt(Tree.Text[Node, Column])) = Tree.Text[Node, Column]; + except + ValIsNumber := False; + end; + if ValIsNumber then + HintText := FormatNumber(Tree.Text[Node, Column]) + else + HintText := StrEllipsis(Tree.Text[Node, Column], SIZE_KB); + end; + // See http://www.heidisql.com/forum.php?t=20458#p20548 + if Sender = DBtree then + LineBreakStyle := hlbForceSingleLine + else + LineBreakStyle := hlbForceMultiLine; +end;} + + +{procedure TMainForm.actLogHorizontalScrollbarExecute(Sender: TObject); +begin + // Toggle visibility of horizontal scrollbar + if TAction(Sender).Checked then + SynMemoSQLLog.ScrollBars := ssBoth + else + SynMemoSQLLog.ScrollBars := ssVertical; +end;} + + +{** + Enable/disable file logging by popupmenuclick +} +{procedure TMainForm.menuLogToFileClick(Sender: TObject); +begin + LogToFile := not LogToFile; + // Save option + AppSettings.ResetPath; + AppSettings.WriteBool(asLogToFile, LogToFile); +end;} + + +{** + Open folder with session logs +} +{procedure TMainForm.menuOpenLogFolderClick(Sender: TObject); +begin + ShellExec('', AppSettings.ReadString(asSessionLogsDirectory)); +end;} + + +{** + A header column of a VirtualTree was "dragged out", which means: + dragged down or up, not to the left or right. + We imitate the behaviour of various applications (fx Outlook) and + hide this dragged column +} +{procedure TMainForm.AnyGridHeaderDraggedOut(Sender: TVTHeader; Column: + TColumnIndex; DropPosition: TPoint); +var + Remaining: TColumnsArray; +begin + // Hide the draggedout column, if it's not the last one + // See also menuToggleAllClick, where hiding all is restricted through the poAllowHideAll option + Remaining := Sender.Columns.GetVisibleColumns; + if Length(Remaining) > 1 then + Sender.Columns[Column].Options := Sender.Columns[Column].Options - [coVisible]; + // Dynamic arrays are free'd when their scope ends, so this should not be required: + SetLength(Remaining, 0); +end;} + + +{procedure TMainForm.AnyGridIncrementalSearch(Sender: TBaseVirtualTree; Node: PVirtualNode; + const SearchText: String; var Result: Integer); +var + CellText: String; + VT: TVirtualStringTree; +begin + // Override VT's default incremental search behaviour. Make it case insensitive. + VT := Sender as TVirtualStringTree; + if VT.FocusedColumn = NoColumn then + Exit; + CellText := VT.Text[Node, VT.FocusedColumn]; + Result := StrLIComp(PChar(CellText), PChar(SearchText), Length(SearchText)); +end;} + + +{procedure TMainForm.ListTablesBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + var ContentRect: TRect); +var + Obj: PDBObject; +begin + PaintAlternatingRowBackground(TargetCanvas, Node, CellRect); + + // Only paint bar in rows + size column + if Column in [1, 2] then begin + Obj := Sender.GetNodeData(Node); + case Column of + 1: PaintColorBar(Obj.Rows, FDBObjectsMaxRows, TargetCanvas, CellRect); + 2: PaintColorBar(Obj.Size, FDBObjectsMaxSize, TargetCanvas, CellRect); + end; + end; +end;} + + +{function TMainForm.GetAlternatingRowBackground(Node: PVirtualNode): TColor; +var + clEven, clOdd: TColor; + isEven: Boolean; +begin + // Alternating row background. See issue #139 + Result := clNone; + clEven := AppSettings.ReadInt(asRowBackgroundEven); + clOdd := AppSettings.ReadInt(asRowBackgroundOdd); + isEven := Node.Index mod 2 = 0; + if IsEven and (clEven <> clNone) then + Result := clEven + else if (not IsEven) and (clOdd <> clNone) then + Result := clOdd; +end;} + + +{procedure TMainForm.PaintAlternatingRowBackground(TargetCanvas: TCanvas; Node: PVirtualNode; CellRect: TRect); +var + BgColor: TColor; +begin + // Apply color + BgColor := GetAlternatingRowBackground(Node); + if BgColor <> clNone then begin + TargetCanvas.Brush.Color := BgColor; + TargetCanvas.FillRect(CellRect); + end; +end;} + + +{procedure TMainForm.PaintColorBar(Value, Max: Extended; TargetCanvas: TCanvas; CellRect: TRect); +var + BarWidth, CellWidth: Integer; +begin + if not AppSettings.ReadBool(asDisplayBars) then + Exit; + + // Add minimal margin to cell edges + InflateRect(CellRect, -1, -1); + CellWidth := CellRect.Right - CellRect.Left; + + // Avoid division by zero, when max is 0 - very rare case but reported in issue #2196. + if (Value > 0) and (Max > 0) then begin + BarWidth := Round(CellWidth / Max * Value); + TargetCanvas.Brush.Color := ColorAdjustBrightness(TargetCanvas.Brush.Color, 20); + TargetCanvas.Pen.Color := ColorAdjustBrightness(TargetCanvas.Brush.Color, -40); + TargetCanvas.RoundRect(CellRect.Left, CellRect.Top, CellRect.Left+BarWidth, CellRect.Bottom, 2, 2); + end; +end;} + + +{** + A row in the process list was selected. Fill SynMemoProcessView with + the SQL of that row. +} +{procedure TMainForm.ListProcessesFocusChanged(Sender: TBaseVirtualTree; + Node: PVirtualNode; Column: TColumnIndex); +var + NodeFocused, EnableControls: Boolean; + SQL: String; +begin + NodeFocused := Assigned(Node); + SQL := ''; + if NodeFocused then begin + SQL := ListProcesses.Text[Node, 7]; + end; + EnableControls := not SQL.IsEmpty; + if EnableControls then begin + SynMemoProcessView.Highlighter := SynSQLSynUsed; + SynMemoProcessView.Text := ListProcesses.Text[Node, 7]; + SynMemoProcessView.Color := clWindow; + end else begin + SynMemoProcessView.Highlighter := nil; + SynMemoProcessView.Text := _('Please select a process with a running query'); + SynMemoProcessView.Color := clBtnFace; + end; + + SynMemoProcessView.Enabled := EnableControls; + pnlProcessView.Enabled := EnableControls; + lblExplainProcess.Enabled := EnableControls and ActiveConnection.Parameters.IsAnyMySQL; + menuExplainProcess.Enabled := lblExplainProcess.Enabled; +end;} + + +{*** + Apply a filter to a Virtual Tree. +} +{procedure TMainForm.editFilterVTChange(Sender: TObject); +begin + // Reset typing timer + TimerFilterVT.Enabled := False; + TimerFilterVT.Enabled := True; + editFilterVT.RightButton.Visible := editFilterVT.Text <> ''; +end;} + + +{procedure TMainForm.editDatabaseTableFilterKeyPress(Sender: TObject; var Key: Char); +begin + if Key = #27 then + (Sender as TButtonedEdit).OnRightButtonClick(Sender); +end;} + + +{procedure TMainForm.TimerFilterVTTimer(Sender: TObject); +begin + // Disable timer to avoid filtering in a loop + TimerFilterVT.Enabled := False; + + // Code moved into this procedure in order to call it by different way + ApplyVTFilter(True); +end;} + + +{procedure TMainForm.ApplyVTFilter(FromTimer: Boolean); +var + Node: PVirtualNode; + VT: TVirtualStringTree; + i: Integer; + match: Boolean; + tab: TTabSheet; + VisibleCount: Cardinal; + CellText: String; + rx: TRegExpr; + OldDataLocalNumberFormat: Boolean; + OldImageIndex: Integer; +begin + // Find the correct VirtualTree that shall be filtered + tab := PageControlMain.ActivePage; + if tab = tabHost then + tab := PageControlHost.ActivePage; + VT := nil; + if tab = tabDatabases then begin + VT := ListDatabases; + FFilterTextDatabases := editFilterVT.Text; + end else if tab = tabVariables then begin + VT := ListVariables; + FFilterTextVariables := editFilterVT.Text; + end else if tab = tabStatus then begin + VT := ListStatus; + FFilterTextStatus := editFilterVT.Text; + end else if tab = tabProcesslist then begin + VT := ListProcesses; + FFilterTextProcessList := editFilterVT.Text; + end else if tab = tabCommandStats then begin + VT := ListCommandStats; + FFilterTextCommandStats := editFilterVT.Text; + end else if tab = tabDatabase then begin + VT := ListTables; + FFilterTextDatabase := editFilterVT.Text; + end else if tab = tabEditor then begin + if ActiveObjectEditor is TfrmTableEditor then + VT := TfrmTableEditor(ActiveObjectEditor).listColumns; + FFilterTextEditor := editFilterVT.Text; + end else if tab = tabData then begin + VT := DataGrid; + FFilterTextData := editFilterVT.Text; + end else if QueryTabs.HasActiveTab and (QueryTabs.ActiveTab.ActiveResultTab <> nil) then begin + VT := ActiveGrid; + QueryTabs.ActiveTab.ActiveResultTab.FilterText := editFilterVT.Text; + end; + if not Assigned(VT) then + Exit; + // Loop through all nodes and hide non matching + Node := VT.GetFirst; + rx := TRegExpr.Create; + rx.ModifierI := True; + rx.Expression := editFilterVT.Text; + if rx.Expression <> '' then try + rx.Exec('abc'); + except + on E:ERegExpr do begin + LogSQL('Filter text is not a valid regular expression: "'+rx.Expression+'"', lcError); + rx.Expression := ''; + end; + end; + VisibleCount := 0; + OldDataLocalNumberFormat := DataLocalNumberFormat; + DataLocalNumberFormat := False; + // Display hour glass instead of X icon + OldImageIndex := editFilterVT.RightButton.ImageIndex; + editFilterVT.RightButton.ImageIndex := 150; + editFilterVT.Repaint; + + VT.BeginUpdate; + while Assigned(Node) do begin + // Don't filter anything if the filter text is empty + match := rx.Expression = ''; + // Search for given text in node's captions + if not match then for i := 0 to VT.Header.Columns.Count - 1 do begin + CellText := VT.Text[Node, i]; + match := rx.Exec(CellText); + if match then + break; + end; + VT.IsVisible[Node] := match; + if match then + inc(VisibleCount); + Node := VT.GetNext(Node); + end; + VT.EndUpdate; + if rx.Expression <> '' then begin + lblFilterVTInfo.Caption := f_('%0:d out of %1:d matching. %2:d hidden.', [VisibleCount, VT.RootNodeCount, VT.RootNodeCount - VisibleCount]); + end else + lblFilterVTInfo.Caption := ''; + + if FromTimer then + VT.Invalidate + else + InvalidateVT(VT, VTREE_LOADED, true); + DataLocalNumberFormat := OldDataLocalNumberFormat; + editFilterVT.RightButton.ImageIndex := OldImageIndex; + rx.Free; +end;} + + +{procedure TMainForm.ApplyFontToGrids; +var + QueryTab: TQueryTab; + ResultTab: TResultTab; + Grid: TVirtualStringTree; + IncrementalSearchActive: Boolean; + AllGrids: TObjectList<TVirtualStringTree>; +begin + // Apply changed settings to all existing data and query grids + LogSQL('Apply grid settings...', lcDebug); + AllGrids := TObjectList<TVirtualStringTree>.Create(False); + IncrementalSearchActive := AppSettings.ReadBool(asIncrementalSearch); + AllGrids.Add(DataGrid); // Data tab grid + AllGrids.Add(QueryGrid); // Mother query grid + for QueryTab in QueryTabs do begin // Query tab child grids + for ResultTab in QueryTab.ResultTabs do begin + AllGrids.Add(ResultTab.Grid); + end; + end; + for Grid in AllGrids do begin + Grid.Font.Name := AppSettings.ReadString(asDataFontName); + Grid.Font.Size := AppSettings.ReadInt(asDataFontSize); + FixVT(Grid, AppSettings.ReadInt(asGridRowLineCount)); + if IncrementalSearchActive then + Grid.IncrementalSearch := isInitializedOnly + else + Grid.IncrementalSearch := isNone; + end; + AllGrids.Free; +end;} + + +{procedure TMainForm.PrepareImageList; +var + IconPack: String; + WantedImageCollection: TComponent; +begin + // Load preferred ImageCollection into VirtualImageList + VirtualImageListMain.Clear; + IconPack := AppSettings.ReadString(asIconPack); + WantedImageCollection := FindComponent('ImageCollection' + IconPack); + if (WantedImageCollection <> nil) and (WantedImageCollection is TImageCollection) then begin + VirtualImageListMain.ImageCollection := WantedImageCollection as TImageCollection; + end else begin + VirtualImageListMain.ImageCollection := ImageCollectionIcons8; + end; + // Add all normal color icons from collection to virtual image list + VirtualImageListMain.Add('', 0, VirtualImageListMain.ImageCollection.Count-1); + // Add all icons again in disabled/grayscale mode, used in TExtForm.PageControlTabHighlight + VirtualImageListMain.AddDisabled('', 0, VirtualImageListMain.ImageCollection.Count-1); +end;} + + +{procedure TMainForm.ListVariablesDblClick(Sender: TObject); +begin + menuEditVariable.Click; +end;} + + +{procedure TMainForm.ListVariablesPaintText(Sender: TBaseVirtualTree; + const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + TextType: TVSTTextType); +var + Idx: PInt64; + i, tmp: Integer; + VarName, SessionVal, GlobalVal: String; + dcat: TDBDatatypeCategoryIndex; + vt: TVirtualStringTree; +begin + vt := Sender as TVirtualStringTree; + Idx := Sender.GetNodeData(Node); + VarName := FVariableNames[Idx^]; + tmp := -1; + for i:=Low(MySQLVariables) to High(MySQLVariables) do begin + if MySQLVariables[i].Name = VarName then begin + tmp := i; + break; + end; + end; + if (tmp=-1) or (not MySQLVariables[tmp].IsDynamic) then begin + // Gray out whole row if the variable is either unknown or not editable + TargetCanvas.Font.Color := GetThemeColor(clGrayText) + end else if Column in [1, 2] then begin + SessionVal := vt.Text[Node, 1]; + GlobalVal := vt.Text[Node, 2]; + if IsInt(SessionVal) or IsInt(GlobalVal) then + dcat := dtcInteger + else if (tmp > -1) and ((MySQLVariables[tmp].EnumValues <> '') or (Pos(UpperCase(SessionVal), 'ON,OFF,0,1,YES,NO')>0)) then + dcat := dtcOther + else + dcat := dtcText; + TargetCanvas.Font.Color := DatatypeCategories[dcat].Color; + end; +end;} + + +{procedure TMainForm.HostListGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); +var + Results: TDBQuery; + Idx: PInt64; + IsIdle: Boolean; +begin + if (Column <> (Sender as TVirtualStringTree).Header.MainColumn) then + exit; + if Sender = ListProcesses then begin + Idx := Sender.GetNodeData(Node); + Results := GridResult(Sender); + Results.RecNo := Idx^; + case Kind of + ikNormal, ikSelected: begin + case Results.Connection.Parameters.NetTypeGroup of + ngMySQL: IsIdle := Results.Col('Info', True) = ''; + ngMSSQL: IsIdle := (Results.Col(6, True) <> 'running') and (Results.Col(6, True) <> 'runnable'); + else IsIdle := False; + end; + if IsIdle then begin + if MakeInt(Results.Col(5, True)) < 60 then + ImageIndex := 151 // Idle, same icon as in lower right status panel + else + ImageIndex := 167 // Long idle thread + end else + ImageIndex := actExecuteQuery.ImageIndex; // Running query + end; + ikOverlay: begin + if IntToStr(Results.Connection.ThreadId) = Results.Col(0, True) then + ImageIndex := 168; // Indicate users own thread id + if CompareText(Results.Col(4, True), 'Killed') = 0 then + ImageIndex := 158; // Broken + end; + else; + end; + end else begin + case Kind of + ikNormal, ikSelected: ImageIndex := 25; + else; + end; + end; +end;} + + +{procedure TMainForm.HostListGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); +var + Idx: PInt64; + Results: TDBQuery; + ValIsBytes, ValIsNumber: Boolean; + ValCount, CommandCount: Int64; + tmpval: Double; +begin + Idx := Sender.GetNodeData(Node); + Results := GridResult(Sender); + // See issue #3416 + if (Results = nil) and (Sender <> ListVariables) then + Exit; + if Results <> nil then + Results.RecNo := Idx^; + + if (Sender = ListStatus) and (Column in [1,2,3]) then begin + CellText := Results.Col(1); + + // Detect value type + try + ValIsNumber := IntToStr(MakeInt(CellText)) = CellText; + except + ValIsNumber := False; + end; + ValIsBytes := ValIsNumber and (Copy(Results.Col(0), 1, 6) = 'Bytes_'); + + // Calculate average values ... + case Column of + 1: begin // Format numeric or byte values + if ValIsBytes then + CellText := FormatByteNumber(CellText) + else if ValIsNumber then + CellText := FormatNumber(CellText); + end; + 2,3: begin // ... per hour/second + if ValIsNumber then begin + ValCount := MakeInt(CellText); + tmpval := ValCount / (FStatusServerUptime / 60 / 60); + if Column = 3 then + tmpval := tmpval / 60 / 60; + if ValIsBytes then + CellText := FormatByteNumber(Trunc(tmpval)) + else if ValIsNumber then + CellText := FormatNumber(tmpval, 1); + end else + CellText := ''; + end; + end; + + end else if Sender = ListCommandStats then begin + CommandCount := MakeInt(Results.Col(1)); + case Column of + 0: begin // Strip "Com_" + CellText := Results.Col(Column); + if CellText.StartsWith('Com_', True) then + CellText := Copy(CellText, 5, Length(CellText)); + CellText := StringReplace(CellText, '_', ' ', [rfReplaceAll] ); + end; + 1: begin // Total Frequency + CellText := FormatNumber(CommandCount); + end; + 2: begin // Average per hour + tmpval := CommandCount / (FCommandStatsServerUptime / 60 / 60); + CellText := FormatNumber(tmpval, 1); + end; + 3: begin // Average per second + tmpval := CommandCount / FCommandStatsServerUptime; + CellText := FormatNumber(tmpval, 1); + end; + 4: begin // Percentage. Take care of division by zero errors and Int64's + tmpval := 100 / Max(FCommandStatsQueryCount, 1) * Max(CommandCount, 1); + CellText := FormatNumber(tmpval, 1) + ' %'; + end; + end; + + end else if Sender = ListVariables then begin + try + case Column of + 0: CellText := FVariableNames[Idx^]; + 1: CellText := FSessionVars.Values[FVariableNames[Idx^]]; + 2: CellText := FGlobalVars.Values[FVariableNames[Idx^]]; + end; + except + CellText := ''; + end; + + end else begin + // Values directly from a query result + CellText := StrEllipsis(Results.Col(Column, True), SIZE_KB*50); + end; +end;} + + +{** + Edit a server variable +} +{procedure TMainForm.menuEditVariableClick(Sender: TObject); +var + Dialog: TfrmEditVariable; +begin + Dialog := TfrmEditVariable.Create(Self); + try + try + Dialog.VarName := ListVariables.Text[ListVariables.FocusedNode, 0]; + Dialog.VarValue := ListVariables.Text[ListVariables.FocusedNode, 1]; + // Refresh list node + if Dialog.ShowModal = mrOK then + InvalidateVT(ListVariables, VTREE_NOTLOADED, False); + except + on E:EVariableError do + ErrorDialog(E.Message); + end; + finally + Dialog.Free; + end; +end;} + + +{procedure TMainForm.DBtreeGetNodeDataSize(Sender: TBaseVirtualTree; var NodeDataSize: Integer); +begin + // Set pointer size of bound TDBObjects + NodeDataSize := SizeOf(TDBObject); +end;} + + +{** + Set text of a treenode before it gets displayed or fetched in any way +} +{procedure TMainForm.DBtreeGetText(Sender: TBaseVirtualTree; Node: + PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType; var CellText: String); +var + DBObjects: TDBObjectList; + DBObj: PDBObject; + i: Integer; + Bytes: Int64; + AllListsCached: Boolean; +begin + DBObj := Sender.GetNodeData(Node); + case Column of + 0: case DBObj.NodeType of + lntNone: CellText := DBObj.Connection.Parameters.SessionPath; + lntDb: CellText := DBObj.Database; + lntGroup: begin + CellText := DBObj.Name; + if Sender.ChildrenInitialized[Node] then + CellText := CellText + ' (' + FormatNumber(Sender.ChildCount[Node]) + ')'; + end; + lntTable..lntEvent: try + if (DBObj.Schema <> '') and (DBObj.Connection.Parameters.NetTypeGroup = ngMSSQL) then + CellText := DBObj.Schema + '.' + DBObj.Name + else + CellText := DBObj.Name; + except + CellText := DBObj.Name; + end; + lntColumn: CellText := DBObj.Column; + end; + 1: if DBObj.Connection.Active then case DBObj.NodeType of + // Calculate and display the sum of all table sizes in ALL dbs if all table lists are cached + lntNone: begin + AllListsCached := true; + for i:=0 to DBObj.Connection.AllDatabases.Count-1 do begin + if not DBObj.Connection.DbObjectsCached(DBObj.Connection.AllDatabases[i]) then begin + AllListsCached := false; + break; + end; + end; + // Will be also set to a negative value by GetTableSize and results of SHOW TABLES + Bytes := -1; + if AllListsCached then begin + Bytes := 0; + for i:=0 to DBObj.Connection.AllDatabases.Count-1 do begin + DBObjects := DBObj.Connection.GetDBObjects(DBObj.Connection.AllDatabases[i]); + Inc(Bytes, DBObjects.DataSize); + end; + end; + if Bytes >= 0 then CellText := FormatByteNumber(Bytes) + else CellText := ''; + end; + // Calculate and display the sum of all table sizes in ONE db, if the list is already cached. + lntDb: begin + if not DBObj.Connection.DbObjectsCached(DBObj.Database) then + CellText := '' + else begin + DBObjects := DBObj.Connection.GetDBObjects(DBObj.Database); + CellText := FormatByteNumber(DBObjects.DataSize); + end; + end; + lntTable: begin + if DBObj.Size >= 0 then + CellText := FormatByteNumber(DBObj.Size) + else + CellText := ''; + end + else CellText := ''; // Applies for views/procs/... which have no size + end; + end; +end;} + + +{** + Set icon of a treenode before it gets displayed +} +{procedure TMainForm.DBtreeGetImageIndex(Sender: TBaseVirtualTree; Node: + PVirtualNode; Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: + Boolean; var ImageIndex: TImageIndex); +var + DBObj: PDBObject; +begin + if Column > 0 then + Exit; + DBObj := Sender.GetNodeData(Node); + case Kind of + ikNormal, ikSelected: begin + ImageIndex := DBObj.ImageIndex; + Ghosted := (DBObj.NodeType = lntNone) and (not DBObj.Connection.Active); + Ghosted := Ghosted or ((DBObj.NodeType = lntDB) + and (not DBObj.Connection.DbObjectsCached(DBObj.Database)) + ); + Ghosted := Ghosted or ((DBObj.NodeType = lntGroup) + and Sender.ChildrenInitialized[Node] + and (Sender.ChildCount[Node] = 0) + ); + Ghosted := Ghosted or ((DBObj.NodeType in [lntTable..lntEvent]) + and (not DBObj.WasSelected) + ); + end; + ikOverlay: + ImageIndex := DBObj.OverlayImageIndex; + end; +end;} + + +{** + Set childcount of an expanding treenode +} +{procedure TMainForm.DBtreeInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal); +var + DBObj: PDBObject; + Columns: TTableColumnList; + DBObjects: TDBObjectList; +begin + DBObj := Sender.GetNodeData(Node); + case DBObj.NodeType of + // Session node expanding + lntNone: begin + Screen.Cursor := crHourglass; + ShowStatusMsg(_('Reading Databases...')); + if Sender.Tag = VTREE_NOTLOADED_PURGECACHE then + DBObj.Connection.RefreshAllDatabases; + ShowStatusMsg; + Sender.Tag := VTREE_LOADED; + InvalidateVT(ListDatabases, VTREE_NOTLOADED, True); + ChildCount := DBObj.Connection.AllDatabases.Count; + Screen.Cursor := crDefault; + end; + // DB node expanding + lntDb: begin + if actGroupObjects.Checked then begin + // Just tables, views, etc. + ChildCount := 6; + end else begin + ShowStatusMsg(_('Reading objects ...')); + Screen.Cursor := crHourglass; + try + ChildCount := DBObj.Connection.GetDBObjects(DBObj.Connection.AllDatabases[Node.Index]).Count; + finally + ShowStatusMsg; + Screen.Cursor := crDefault; + end; + end; + end; + lntGroup: begin + ChildCount := 0; + DBObjects := DBObj.Connection.GetDBObjects(DBObj.Database, False, DBObj.GroupType); + ChildCount := DBObjects.Count; + end; + lntTable: + if GetParentFormOrFrame(Sender) is TfrmSelectDBObject then begin + Columns := DBObj.TableColumns; + ChildCount := Columns.Count; + end; + end; +end;} + + +{** + Set initial options of a treenode and bind DBobject to node which holds the relevant + connection object, probably its database and probably its table/view/... specific properties +} +{procedure TMainForm.DBtreeInitNode(Sender: TBaseVirtualTree; ParentNode, Node: + PVirtualNode; var InitialStates: TVirtualNodeInitStates); +var + Item, ParentObj: PDBObject; + DBObjects: TDBObjectList; + Columns: TTableColumnList; +begin + Item := Sender.GetNodeData(Node); + if (not Assigned(ParentNode)) or (ParentNode = nil) then begin + Item^ := TDBObject.Create(FConnections[Node.Index]); + // Ensure plus sign is visible for root (and dbs, see below) + Include(InitialStates, ivsHasChildren); + end else begin + ParentObj := Sender.GetNodeData(ParentNode); + case ParentObj.NodeType of + lntNone: begin + Item^ := TDBObject.Create(ParentObj.Connection); + Item.NodeType := lntDb; + Item.Database := Item.Connection.AllDatabases[Node.Index]; + Include(InitialStates, ivsHasChildren); + end; + lntDb: begin + if actGroupObjects.Checked then begin + Item^ := TDBObject.Create(ParentObj.Connection); + Item.NodeType := lntGroup; + case Node.Index of + 0: begin Item.GroupType := lntTable; Item.Name := _('Tables'); end; + 1: begin Item.GroupType := lntView; Item.Name := _('Views'); end; + 2: begin Item.GroupType := lntProcedure; Item.Name := _('Procedures'); end; + 3: begin Item.GroupType := lntFunction; Item.Name := _('Functions'); end; + 4: begin Item.GroupType := lntTrigger; Item.Name := _('Triggers'); end; + 5: begin Item.GroupType := lntEvent; Item.Name := _('Events'); end; + end; + Item.Database := ParentObj.Database; + InitialStates := InitialStates + [ivsHasChildren]; + end else begin + DBObjects := ParentObj.Connection.GetDBObjects(ParentObj.Database); + Item^ := DBObjects[Node.Index]; + if (GetParentFormOrFrame(Sender) is TfrmSelectDBObject) and (Item.NodeType = lntTable) then + Include(InitialStates, ivsHasChildren); + end; + end; + lntGroup: begin + DBObjects := ParentObj.Connection.GetDBObjects(ParentObj.Database, False, ParentObj.GroupType); + Item^ := DBObjects[Node.Index]; + if (GetParentFormOrFrame(Sender) is TfrmSelectDBObject) and (Item.NodeType = lntTable) then + Include(InitialStates, ivsHasChildren); + end; + lntTable: begin + Item^ := TDBObject.Create(ParentObj.Connection); + Item.NodeType := lntColumn; + Columns := ParentObj.TableColumns; + Item.Database := ParentObj.Database; + Item.Name := ParentObj.Name; + Item.Column := Columns[Node.Index].Name; + end; + end; + end; +end;} + + +{** + Selection in database tree has changed +} +{procedure TMainForm.DBtreeFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex); +var + DBObj, PrevDBObj, ParentDBObj: PDBObject; + MainTabToActivate: TTabSheet; + EnteringSession: Boolean; +begin + // Set wanted main tab and call SetMainTab later, when all lists have been invalidated + MainTabToActivate := nil; + PrevDBObj := nil; + ParentDBObj := nil; + + if Assigned(Node) then begin + LogSQL('DBtreeFocusChanged, Node level: '+IntToStr(Sender.GetNodeLevel(Node))+', FTreeRefreshInProgress: '+IntToStr(Integer(FTreeRefreshInProgress)), lcDebug); + + // Post pending UPDATE + if (DataGridResult<>nil) and DataGridResult.Modified then + actDataPostChangesExecute(DataGrid); + + DBObj := Sender.GetNodeData(Node); + DBObj.WasSelected := True; + FActiveDbObj := TDBObject.Create(DBObj.Connection); + FActiveDbObj.Assign(DBObj^); + if Assigned(Node.Parent) and (DBtree.GetNodeLevel(Node) > 0) then + ParentDBObj := Sender.GetNodeData(Node.Parent); + + case FActiveDbObj.NodeType of + lntNone: begin + if (not DBtree.Dragging) and (not QueryTabs.HasActiveTab) then + MainTabToActivate := tabHost; + FActiveDbObj.Connection.Database := ''; + end; + lntDb, lntGroup: begin + // Selecting a database can cause an SQL error if the db was deleted from outside. Select previous node in that case. + try + FActiveDbObj.Connection.Database := FActiveDbObj.Database; + except on E:EDbError do begin + ErrorDialog(E.Message); + SelectNode(DBtree, TreeClickHistoryPrevious); + Exit; + end; + end; + if (not DBtree.Dragging) and (not QueryTabs.HasActiveTab) then + MainTabToActivate := tabDatabase; + FActiveObjectGroup := FActiveDbObj.GroupType; + end; + lntTable..lntEvent: begin + try + FActiveDbObj.Connection.Database := FActiveDbObj.Database; + except on E:EDbError do begin + ErrorDialog(E.Message); + SelectNode(DBtree, TreeClickHistoryPrevious); + Exit; + end; + end; + + // Retrieve columns of current table or view. Mainly used in datagrid. + SelectedTableColumns.Clear; + SelectedTableKeys.Clear; + SelectedTableForeignKeys.Clear; + AppSettings.SessionPath := GetRegKeyTable; + SelectedTableTimestampColumns.Text := AppSettings.ReadString(asTimestampColumns); + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); + try + if FActiveDbObj.NodeType in [lntTable, lntView] then begin + SelectedTableColumns := FActiveDbObj.TableColumns; + try + SelectedTableKeys := FActiveDbObj.TableKeys; + except // No show stopper, happening when a view references a renamed table column, see #1130 + on E:EDbError do + ErrorDialog(_('This view probably contains an error in its code.')+sLineBreak+sLineBreak+E.Message); + end; + SelectedTableForeignKeys := FActiveDbObj.TableForeignKeys; + end; + PlaceObjectEditor(FActiveDbObj); + // When a table is clicked in the tree, and the current + // tab is a Host or Database tab, switch to showing table columns. + if (PagecontrolMain.ActivePage = tabHost) or (PagecontrolMain.ActivePage = tabDatabase) then + MainTabToActivate := tabEditor; + if DataGrid.Tag = VTREE_LOADED then + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); + // Update the list of columns + RefreshHelperNode(TQueryTab.HelperNodeColumns); + except on E:EDbError do + ErrorDialog(E.Message); + end; + + if Assigned(ParentDBObj) then + FActiveObjectGroup := ParentDBObj.GroupType; + end; + end; + + if TreeClickHistoryPrevious(True) <> nil then + PrevDBObj := Sender.GetNodeData(TreeClickHistoryPrevious(True)); + + // When clicked node is from a different connection than before, do session specific stuff here: + try + EnteringSession := (FActiveDbObj <> nil) + and ((PrevDBObj = nil) or (PrevDBObj.Connection <> FActiveDbObj.Connection)); + except + on E:EAccessViolation do begin + LogSQL(E.ClassName+' while moving focus in tree.', lcError); + EnteringSession := True; + end; + end; + if EnteringSession then begin + LogSQL(f_('Entering session "%s"', [FActiveDbObj.Connection.Parameters.SessionPath]), lcInfo); + RefreshHelperNode(TQueryTab.HelperNodeHistory); + RefreshHelperNode(TQueryTab.HelperNodeProfile); + case FActiveDbObj.Connection.Parameters.NetTypeGroup of + ngMySQL: + SynSQLSynUsed.SQLDialect := sqlMySQL; + ngMSSQL: + SynSQLSynUsed.SQLDialect := sqlMSSQL2K; + ngPgSQL: + SynSQLSynUsed.SQLDialect := sqlPostgres; + ngSQLite: + SynSQLSynUsed.SQLDialect := sqlStandard; + ngInterbase: + SynSQLSynUsed.SQLDialect := sqlInterbase6; + else + raise Exception.CreateFmt(_(MsgUnhandledNetType), [Integer(FActiveDbObj.Connection.Parameters.NetType)]); + end; + // Extend predefined MySQLFunctions from SynHighlighterSQL with our own functions list + SynSQLSynUsed.FunctionNames.BeginUpdate; + SynSQLSynUsed.FunctionNames.Clear; + SynSQLSynUsed.FunctionNames.AddStrings(FActiveDbObj.Connection.SQLFunctions.Names); + SynSQLSynUsed.FunctionNames.EndUpdate; + end; + + if (FActiveDbObj <> nil) + and (FActiveDbObj.NodeType <> lntNone) + and ( + (PrevDBObj = nil) + or (PrevDBObj.Connection <> FActiveDbObj.Connection) + or (PrevDBObj.Database <> FActiveDbObj.Database) + or (PrevDBObj.GroupType <> FActiveObjectGroup) + ) then + InvalidateVT(ListTables, VTREE_NOTLOADED, True); + if FActiveDbObj.NodeType = lntGroup then + InvalidateVT(ListTables, VTREE_NOTLOADED, True); + + SetTabCaption(tabHost.PageIndex, FActiveDbObj.Connection.Parameters.SessionName); + SetTabCaption(tabDatabase.PageIndex, _('Database')+': '+FActiveDbObj.Connection.Database); + ShowStatusMsg(FActiveDbObj.Connection.Parameters.NetTypeName(False)+' '+FActiveDbObj.Connection.ServerVersionStr, 3); + end else begin + LogSQL('DBtreeFocusChanged without node.', lcDebug); + FreeAndNil(FActiveDbObj); + tabHost.Caption := _('Host'); + tabDatabase.Caption := _('Database'); + // Clear server version panel + ShowStatusMsg('', 3); + end; + + if (FActiveDbObj = nil) or (PrevDBObj = nil) or (PrevDBObj.Connection <> FActiveDbObj.Connection) then begin + TimerConnected.OnTimer(Sender); + TimerHostUptime.OnTimer(Sender); + InvalidateVT(ListDatabases, VTREE_NOTLOADED, False); + InvalidateVT(ListVariables, VTREE_NOTLOADED, False); + InvalidateVT(ListStatus, VTREE_NOTLOADED, False); + InvalidateVT(ListProcesses, VTREE_NOTLOADED, False); + InvalidateVT(ListCommandstats, VTREE_NOTLOADED, False); + InvalidateVT(ListTables, VTREE_NOTLOADED, False); + ValidateQueryControls(Self); + end; + + // Make wanted tab visible before activating, to avoid unset tab on Wine + if Assigned(MainTabToActivate) then + MainTabToActivate.TabVisible := True; + if not FTreeRefreshInProgress then begin + SetMainTab(MainTabToActivate); + tabDatabase.TabVisible := (FActiveDbObj <> nil) and (FActiveDbObj.NodeType <> lntNone); + tabEditor.TabVisible := (FActiveDbObj <> nil) and (FActiveDbObj.NodeType in [lntTable..lntEvent]); + tabData.TabVisible := (FActiveDbObj <> nil) and (FActiveDbObj.NodeType in [lntTable, lntView]); + end; + + // Store click history item + SetLength(FTreeClickHistory, Length(FTreeClickHistory)+1); + FTreeClickHistory[Length(FTreeClickHistory)-1] := Node; + + DBTree.InvalidateColumn(0); + FixQueryTabCloseButtons; + SetWindowCaption; +end;} + + +{procedure TMainForm.DBtreeFocusChanging(Sender: TBaseVirtualTree; OldNode, + NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; + var Allowed: Boolean); +begin + // Check if some editor has unsaved changes + if Assigned(ActiveObjectEditor) and Assigned(NewNode) and (NewNode <> OldNode) and (not FTreeRefreshInProgress) then begin + Allowed := not (ActiveObjectEditor.DeInit in [mrAbort, mrCancel]); + DBTree.Selected[DBTree.FocusedNode] := not Allowed; + end else + Allowed := NewNode <> OldNode; +end;} + + +{procedure TMainForm.DBtreeFreeNode(Sender: TBaseVirtualTree; Node: PVirtualNode); +var +// DBObj: PDBObject; + i: Integer; +begin + // Keep track of the previously selected tree node's state, to avoid AVs in OnFocusChanged() + for i:=0 to Length(FTreeClickHistory)-1 do begin + if Node = FTreeClickHistory[i] then + FTreeClickHistory[i] := nil; + end; + // TODO: Free object if its host or db. Tables/views/... already get freed in Connection.ClearDBObjects + // does not work here when table is focused, for some reason: + // DBObj := Sender.GetNodeData(Node); + // if Assigned(DBObj^) and (DBObj.NodeType in [lntNone, lntDb]) then + // logsql('freeing node: type #'+inttostr(integer(dbobj.NodeType))+' name: '+dbobj.database); + // FreeAndNil(DBObj^); + // end; +end;} + + +{function TMainForm.TreeClickHistoryPrevious(MayBeNil: Boolean=False): PVirtualNode; +var + i: Integer; +begin + // Navigate to previous or next existant clicked node + Result := nil; + for i:=High(FTreeClickHistory) downto Low(FTreeClickHistory) do begin + if MayBeNil or (FTreeClickHistory[i] <> nil) then begin + Result := FTreeClickHistory[i]; + break; + end; + end; +end;} + + +{procedure TMainForm.ConnectionReady(Connection: TDBConnection; Database: String); +begin + // Manually trigger changed focused tree node, to display the right server vendor + // and version. Also required on reconnects. + DBtree.OnFocusChanged(DBtree, DBtree.FocusedNode, DBtree.FocusedColumn); +end;} + + +{procedure TMainForm.DatabaseChanged(Connection: TDBConnection; Database: String); +begin + // Immediately force db icons to repaint, so the user sees the active db state + DBtree.Repaint; + + // Clear Filter issue 3466 + FFilterTextDatabase := ''; + + if QueryTabs.ActiveHelpersTree <> nil then + QueryTabs.ActiveHelpersTree.Invalidate; +end;} + + +{procedure TMainForm.ObjectnamesChanged(Connection: TDBConnection; Database: String); +var + DBObjects: TDBObjectList; + Obj: TDBObject; + TableNames, ProcNames: TStringList; +begin + // Tell SQL highlighter about names of tables and procedures in selected database + if (ActiveConnection <> Connection) or (Database <> Connection.Database) then + Exit; + SynSQLSynUsed.TableNames.Clear; + SynSQLSynUsed.ProcNames.Clear; + if Connection.DbObjectsCached(Database) then begin + DBObjects := Connection.GetDBObjects(Database); + TableNames := TStringList.Create; + TableNames.BeginUpdate; + ProcNames := TStringList.Create; + ProcNames.BeginUpdate; + for Obj in DBObjects do begin + case Obj.NodeType of + lntTable, lntView: begin + // Slow highlighter enhanced by uschuster. See http://www.heidisql.com/forum.php?t=16307 + // ... and here: https://github.com/SynEdit/SynEdit/issues/28 + TableNames.Add(Obj.Name); + end; + lntProcedure, lntFunction: begin + ProcNames.Add(Obj.Name); + end; + end; + end; + TableNames.EndUpdate; + ProcNames.EndUpdate; + SynSQLSynUsed.TableNames.Text := TableNames.Text; + SynSQLSynUsed.ProcNames.Text := ProcNames.Text; + TableNames.Free; + ProcNames.Free; + end; +end;} + + +{procedure TMainForm.DBtreeDblClick(Sender: TObject); +var + DBObj: PDBObject; + m: TSynMemo; +begin + // Paste DB or table name into query window on treeview double click. + if AppSettings.ReadBool(asDoubleClickInsertsNodeText) and QueryTabs.HasActiveTab and Assigned(DBtree.FocusedNode) then begin + DBObj := DBtree.GetNodeData(DBtree.FocusedNode); + if DBObj.NodeType in [lntDb, lntTable..lntEvent] then begin + m := QueryTabs.ActiveMemo; + m.DragDrop(Sender, m.CaretX, m.CaretY); + end; + end; +end;} + + +{procedure TMainForm.DBtreeExpanded(Sender: TBaseVirtualTree; + Node: PVirtualNode); +begin + // Table and database filter both need initialized children + Sender.ReinitChildren(Node, False); + editDatabaseTableFilterChange(Self); +end;} + + +{procedure TMainForm.DBtreeExpanding(Sender: TBaseVirtualTree; + Node: PVirtualNode; var Allowed: Boolean); +var + DBObj: PDBObject; + GNode: PVirtualNode; +begin + // Auto-init children of sibling groups + DBObj := Sender.GetNodeData(Node); + if DBObj.NodeType = lntGroup then begin + GNode := Sender.GetFirstChild(Node.Parent); + while Assigned(GNode) do begin + if not Sender.ChildrenInitialized[GNode] then begin + Sender.ReinitChildren(GNode, False); + end; + GNode := Sender.GetNextSibling(GNode); + end; + end; +end;} + + +{procedure TMainForm.DBtreePaintText(Sender: TBaseVirtualTree; const + TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; TextType: + TVSTTextType); +var + DBObj: PDBObject; + WalkNode: PVirtualNode; +begin + // Grey out non-current connection nodes, and rather unimportant "Size" column + DBObj := Sender.GetNodeData(Node); + if DBObj.Connection <> ActiveConnection then + TargetCanvas.Font.Color := clGrayText + else if (Column = 1) and (DBObj.NodeType in [lntTable..lntEvent]) then + TargetCanvas.Font.Color := clGrayText; + + // Set bold text if painted node is in focused path + if (Column = Sender.Header.MainColumn) then begin + WalkNode := Sender.FocusedNode; + while Assigned(WalkNode) do begin + if WalkNode = Node then begin + TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; + Break; + end; + try + // This crashes in some situations, which I could never reproduce. + // See uploaded crash reports and issue #1270. + WalkNode := Sender.NodeParent[WalkNode]; + except + on E:EAccessViolation do begin + LogSQL('DBtreePaintText, NodeParent: '+E.Message, lcError); + Break; + end; + end; + end; + end; +end;} + + +{** + Refresh the whole tree +} +{procedure TMainForm.RefreshTree(FocusNewObject: TDBObject=nil); +var + DBNode: PVirtualNode; + OnlyDBNode, Expanded: Boolean; + SessNode: PVirtualNode; +begin + // This refreshes exactly one session node and all its db and table nodes. + // Also, tries to focus the previous focused object, if present. + + // Object editors call RefreshTree in order to make a just created object visible: + OnlyDBNode := FocusNewObject <> nil; + + // Remember currently selected object + if FocusNewObject = nil then begin + FocusNewObject := TDBObject.Create(ActiveConnection); + if FActiveDbObj <> nil then + FocusNewObject.Assign(FActiveDbObj); + end; + + // ReInit tree population + FTreeRefreshInProgress := True; + SelectNode(DBtree, nil); + try + if not OnlyDBNode then begin + FocusNewObject.Connection.ClearAllDbObjects; + FocusNewObject.Connection.RefreshAllDatabases; + SessNode := GetRootNode(DBtree, FocusNewObject.Connection); + if Assigned(SessNode) then begin + Expanded := DBtree.Expanded[SessNode]; + DBtree.ResetNode(SessNode); + DBtree.Expanded[SessNode] := Expanded; + end; + end else begin + FocusNewObject.Connection.ClearDbObjects(FocusNewObject.Database); + DBNode := FindDbNode(DBtree, FocusNewObject.Connection, FocusNewObject.Database); + if Assigned(DBNode) then + DBtree.ResetNode(DBNode); + end; + + // Reselect active or new database if present. Could have been deleted or renamed. + try + if FocusNewObject.NodeType in [lntTable..lntEvent] then + ActiveDBObj := FocusNewObject; + if not Assigned(DBtree.FocusedNode) then + SetActiveDatabase(FocusNewObject.Database, FocusNewObject.Connection); + if not Assigned(DBtree.FocusedNode) then + SetActiveDatabase('', FocusNewObject.Connection); + except + end; + if not Assigned(DBtree.FocusedNode) then + raise Exception.Create(_('Could not find node to focus.')); + + finally + FTreeRefreshInProgress := False; + // Tree node filtering needs a hit in special cases, e.g. after a db was dropped + if editDatabaseFilter.Text <> '' then + editDatabaseFilter.OnChange(editDatabaseFilter); + if editTableFilter.Text <> '' then + editTableFilter.OnChange(editTableFilter); + if editFilterVT.Text <> '' then + ApplyVTFilter(False); + end; +end;} + + +{** + Find a database node in the tree by passing its name +} +{function TMainForm.FindDBNode(Tree: TBaseVirtualTree; Connection: TDBConnection; db: String): PVirtualNode; +var + DBObj: PDBObject; + n, DBNode: PVirtualNode; +begin + Result := nil; + n := GetRootNode(Tree, Connection); + DBNode := Tree.GetFirstChild(n); + while Assigned(DBNode) do begin + DBObj := Tree.GetNodeData(DBNode); + if DBObj.Database = db then begin + Result := DBNode; + Break; + end; + DBNode := Tree.GetNextSibling(DBNode); + end; +end;} + + +{** + Expand all db nodes +} +{procedure TMainForm.menuTreeExpandAllClick(Sender: TObject); +begin + DBtree.FullExpand; + DBtree.ScrollIntoView(DBtree.FocusedNode, False); +end;} + +{** + Collapse all db nodes +} +{procedure TMainForm.menuToggleAllClick(Sender: TObject); +var + Grid: TVirtualStringTree; + Col: TColumnIndex; + VisibleColCount: Integer; + DoHide, AllowHideAll: Boolean; +begin + // Toggle visibility of all columns in list + // Always leave one column visible, synced with poAllowHideAll from popupListHeader.Options + // Logsql(PopupComponent(Sender).Name+': '+PopupComponent(Sender).ClassName); + Grid := PopupComponent(Sender) as TVirtualStringTree; + + VisibleColCount := 0; + Col := Grid.Header.Columns.GetFirstColumn; + while Col > NoColumn do begin + if coVisible in Grid.Header.Columns[Col].Options then + Inc(VisibleColCount); + Col := Grid.Header.Columns.GetNextColumn(Col); + end; + DoHide := VisibleColCount = Grid.Header.Columns.Count; + AllowHideAll := poAllowHideAll in popupListHeader.Options; + + Col := Grid.Header.Columns.GetFirstColumn; + while Col > NoColumn do begin + if DoHide and ((Col <> Grid.Header.Columns.GetFirstColumn) or AllowHideAll) then + Grid.Header.Columns[Col].Options := Grid.Header.Columns[Col].Options - [coVisible] + else + Grid.Header.Columns[Col].Options := Grid.Header.Columns[Col].Options + [coVisible]; + Col := Grid.Header.Columns.GetNextColumn(Col); + end; + +end;} + +{procedure TMainForm.menuTreeCollapseAllClick(Sender: TObject); +var + n: PVirtualNode; + i: Integer; +begin + n := DBtree.GetFirstChild(DBtree.GetFirst); + for i := 0 to DBtree.GetFirst.ChildCount - 1 do begin + DBtree.FullCollapse(n); + n := DBtree.GetNextSibling(n); + end; + DBtree.ScrollIntoView(DBtree.FocusedNode, False); +end;} + + +{procedure TMainForm.editFilterSearchChange(Sender: TObject); +var + Clause, Line, Condition: String; + Conditions: TStringList; + i: Integer; + ed: TEdit; + Conn: TDBConnection; + rx: TRegExpr; +begin + ed := TEdit(Sender); + Clause := ''; + if ed.Text <> '' then begin + + Conn := ActiveConnection; + rx := TRegExpr.Create; + rx.ModifierI := True; + Conditions := TStringList.Create; + for i:=0 to SelectedTableColumns.Count-1 do begin + // The normal case: do a LIKE comparison + Condition := '''%' + Conn.EscapeString(ed.Text, True, False)+'%'''; + Condition := Conn.GetSQLSpecifity(spLikeCompare, [SelectedTableColumns[i].CastAsText, Condition]); + if not SelectedTableColumns[i].DataType.ValueMustMatch.IsEmpty then begin + // Use an exact comparison for some PostgreSQL data types to overcome SQL errors, e.g. UUID, INT etc. + // Also, prevent other errors by matching the value against a certain regular expression. + // If it does not match, leave this column away. + // See http://www.heidisql.com/forum.php?t=20953 + rx.Expression := SelectedTableColumns[i].DataType.ValueMustMatch; + if rx.Exec(ed.Text) then + Condition := Conn.QuoteIdent(SelectedTableColumns[i].Name) + '=' + Conn.EscapeString(ed.Text) + else + Condition := ''; + end; + if not Condition.IsEmpty then + Conditions.Add(Condition); + end; + rx.Free; + + Line := ''; + for i:=0 to Conditions.Count-1 do begin + if i > 0 then + Conditions[i] := ' OR ' + Conditions[i]; + // Add linebreak near right window edge + if (not Line.IsEmpty) and (Length(Line + Conditions[i]) >= SynMemoFilter.CharsInWindow-5) then begin + Clause := Clause + Line + sLineBreak; + Line := ''; + end; + Line := Line + Conditions[i]; + end; + Clause := Clause + Line + Conn.LikeClauseTail; + + end; + SynMemoFilter.UndoList.AddGroupBreak; + SynMemoFilter.SelectAll; + SynMemoFilter.SelText := Clause; +end;} + + +{procedure TMainForm.SynMemoFilterStatusChange(Sender: TObject; Changes: TSynStatusChanges); +var + TextHeight: Integer; + LineCount: Integer; + PanelHeight: Integer; +const + MinDisplayLineCount = 2; + MaxDisplayLineCount = 8; +begin + actClearFilterEditor.Enabled := (Sender as TSynMemo).GetTextLen > 0; + + LineCount := SynMemoFilter.DisplayLineCount; + LineCount := Min(LineCount, MaxDisplayLineCount); + LineCount := Max(LineCount, MinDisplayLineCount); + TextHeight := LineCount * SynMemoFilter.LineHeight + 10; + PanelHeight := pnlFilter.Height + (TextHeight - SynMemoFilter.Height); + PanelHeight := Max(PanelHeight, btnFilterApply.Top + btnFilterApply.Height + 5); + if PanelHeight <> pnlFilter.Height then + pnlFilter.Height := PanelHeight; +end;} + + +{procedure TMainForm.ToggleFilterPanel(ForceVisible: Boolean = False); +var + ShowIt: Boolean; +begin + ShowIt := ForceVisible or (not pnlFilter.Visible); + tbtnDataFilter.Down := ShowIt; + pnlFilter.Visible := ShowIt; +end;} + + +{procedure TMainForm.EnableDataTab(Enable: Boolean); +begin + // Disable data grid to prevent accessing results of disconnected sessions + // Also, no data for routines + DataGrid.Enabled := Enable; + pnlDataTop.Enabled := Enable; + pnlFilter.Enabled := Enable; + if Enable then + lblSorryNoData.Parent := tabData + else + lblSorryNoData.Parent := DataGrid; +end;} + + +{procedure TMainForm.editFilterSearchEnter(Sender: TObject); +begin + // Enables triggering apply button with Enter + btnFilterApply.Default := True; +end;} + + +{procedure TMainForm.editFilterSearchExit(Sender: TObject); +begin + btnFilterApply.Default := False; +end;} + + + +{** + A grid cell fetches its text content +} +{procedure TMainForm.AnyGridGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); +var + EditingAndFocused, IsScientific: Boolean; + RowNumber: PInt64; + Results: TDBQuery; + Timestamp: Int64; + DotPos, i, NumZeros, NumDecimals, KeepDecimals: Integer; + ResultCol: Integer; +begin + if Column = -1 then + Exit; + if TextType <> ttNormal then + Exit; + ResultCol := Column - 1; + if ResultCol < 0 then begin + CellText := (Node.Index +1).ToString; + Exit; + end; + + EditingAndFocused := Sender.IsEditing and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn); + Results := GridResult(Sender); + if (Results = nil) or (not Results.Connection.Active) then begin + EnableDataTab(False); + Exit; + end; + // Happens in some crashes, see issue #2462 + if ResultCol >= Results.ColumnCount then + Exit; + + RowNumber := Sender.GetNodeData(Node); + Results.RecNo := RowNumber^; + if Results.IsNull(ResultCol) and (not EditingAndFocused) then + CellText := TEXT_NULL + else begin + case Results.DataType(ResultCol).Category of + dtcInteger, dtcReal: begin + // This is a bit crappy... + // UNIX timestamps get *copied* as integers, but *displayed* and *edited* as date/time values. + // Normal integers are *copied* and *edited* as raw numbers, but probably *displayed* as formatted numbers. + if FGridCopying then begin + CellText := Results.Col(ResultCol); + end else if HandleUnixTimestampColumn(Sender, Column) then begin + try + Timestamp := Trunc(StrToFloat(Results.Col(ResultCol), FFormatSettings)); + Dec(Timestamp, FTimeZoneOffset); + CellText := DateTimeToStr(UnixToDateTime(Timestamp)); + except + // EConvertError in StrToFloat or EInvalidOp in Trunc or... + on E:Exception do begin + CellText := Results.Col(ResultCol); + LogSQL('Error when calculating Unix timestamp from "'+CellText+'": '+E.Message, lcError); + end; + end; + end else begin + CellText := Results.Col(ResultCol); + + // Keep only wanted number of trailing zeros after decimal separator + // Bug fixed: Do not cut trailing zeros in scientific values like 2.0e30 => 2.0e3 + if (Results.DataType(ResultCol).Category = dtcReal) and (AppSettings.ReadInt(asRealTrailingZeros) >= 0) then begin + DotPos := Pos('.', CellText); + IsScientific := ContainsText(CellText, 'e'); + if (not IsScientific) and (DotPos > 0) then begin + NumZeros := 0; + NumDecimals := 0; + for i:=DotPos+1 to Length(CellText) do begin + Inc(NumDecimals); + if CellText[i] = '0' then + Inc(NumZeros) + else + NumZeros := 0; + end; + KeepDecimals := Max(NumDecimals - NumZeros, AppSettings.ReadInt(asRealTrailingZeros)); + CellText := Copy(CellText, 1, Length(CellText) - (NumDecimals - KeepDecimals)); + if CellText[Length(CellText)] = '.' then + CellText := Copy(CellText, 1, Length(CellText)-1); + end; + end; + + if DataLocalNumberFormat and (not EditingAndFocused) then + CellText := FormatNumber(CellText, True); + end; + end; + dtcBinary, dtcSpatial: begin + if actBlobAsText.Checked then + CellText := Results.Col(ResultCol) + else + CellText := Results.HexValue(ResultCol, False); + end; + else begin + CellText := Results.Col(ResultCol); + if (Length(CellText) = GRIDMAXDATA) and (not Results.HasFullData) and (Sender = DataGrid) then + CellText := CellText + ' [...]'; + end; + end; + end; +end;} + + +{procedure TMainForm.CalcNullColors; +var + dtc: TDBDatatypeCategoryIndex; + h, l, s: Word; +begin + for dtc:=Low(DatatypeCategories) to High(DatatypeCategories) do begin + ColorRGBToHLS(DatatypeCategories[dtc].Color, h, l, s); + Inc(l, COLORSHIFT_NULLFIELDS); + s := Max(0, s-2*COLORSHIFT_NULLFIELDS); + DatatypeCategories[dtc].NullColor := ColorHLSToRGB(h, l, s); + end; +end;} + + +{** + Cell in data- or query grid gets painted. Colorize font. This procedure is + called extremely often for repainting the grid cells. Keep it highly optimized. +} +{procedure TMainForm.AnyGridPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); +var + cl: TColor; + r: TDBQuery; + RowNumber: PInt64; + ResultCol: Integer; +begin + if Column = NoColumn then + Exit; + ResultCol := Column - 1; + if ResultCol < 0 then begin + TargetCanvas.Font.Color := clGrayText; + //TargetCanvas.Font.Style := [TFontStyle.fsItalic]; + Exit; + end; + + r := GridResult(Sender); + RowNumber := Sender.GetNodeData(Node); + r.RecNo := RowNumber^; + + // Make primary key columns bold + if r.ColIsPrimaryKeyPart(ResultCol) then + TargetCanvas.Font.Style := TargetCanvas.Font.Style + [fsBold]; + + // Do not apply any color on a selected, highlighted cell to keep readability + if (vsSelected in Node.States) and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn) then + cl := GetThemeColor(clHighlightText) + else if r.IsNull(ResultCol) then + cl := DatatypeCategories[r.DataType(ResultCol).Category].NullColor + else + cl := DatatypeCategories[r.DataType(ResultCol).Category].Color; + TargetCanvas.Font.Color := cl; +end;} + + +{procedure TMainForm.AnyGridAfterCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); +var + Results: TDBQuery; + RowNum: PInt64; + ResultCol: Integer; +begin + // Don't waist time + if Column = NoColumn then + Exit; + ResultCol := Column - 1; + if ResultCol < 0 then + Exit; + // Paint a red triangle at the top left corner of the cell + Results := GridResult(Sender); + RowNum := Sender.GetNodeData(Node); + Results.RecNo := RowNum^; + if Results.Modified(ResultCol) then + VirtualImageListMain.Draw(TargetCanvas, CellRect.Left, CellRect.Top, 111); +end;} + + +{** + Header column in datagrid clicked. + Left button: handle ORDER BY + Right button: show column selection box +} +{procedure TMainForm.DataGridHeaderClick(Sender: TVTHeader; HitInfo: TVTHeaderHitInfo); +var + frm: TForm; + ColName: String; + SortItem: TSortItem; + SortOrder: TSortItemOrder; +begin + if HitInfo.Column = NoColumn then + Exit; + if HitInfo.Button = mbLeft then begin + // Header click disabled + if not AppSettings.ReadBool(asColumnHeaderClick) then + Exit; + ColName := Sender.Columns[HitInfo.Column].Text; + // Add a new order column after a columns title has been clicked + // Check if order column is already existant + SortItem := FDataGridSortItems.FindByColumn(ColName); + if Assigned(SortItem) then begin + // AddOrderCol is already in the list. Switch its direction: + // ASC > DESC > [delete col] + if SortItem.Order = sioAscending then + SortItem.Order := sioDescending + else + FDataGridSortItems.Remove(SortItem); + end + else begin + if KeyPressed(VK_SHIFT) then + SortOrder := sioDescending + else + SortOrder := sioAscending; + FDataGridSortItems.AddNew(ColName, SortOrder); + LogSQL('Created sorting for column '+ColName+'/'+Integer(SortOrder).ToString+' in TMainForm.DataGridHeaderClick', lcDebug); + end; + + // Refresh grid, and remember X scroll offset, so the just clicked column is still at the same place. + FDataGridLastClickedColumnHeader := HitInfo.Column; + FDataGridLastClickedColumnLeftPos := Sender.Columns[HitInfo.Column].Left; + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); + + end else begin + frm := TfrmColumnSelection.Create(Self); + // Position new form relative to btn's position + frm.Top := HitInfo.Y + DataGrid.ClientOrigin.Y - Integer(DataGrid.Header.Height); + frm.Left := HitInfo.X + DataGrid.ClientOrigin.X; + // Display form + frm.Show; + end; +end;} + + +{procedure TMainForm.actDataSetNullExecute(Sender: TObject); +var + RowNum: PInt64; + Grid: TVirtualStringTree; + Results: TDBQuery; +begin + // Set cell to NULL value + Grid := ActiveGrid; + RowNum := Grid.GetNodeData(Grid.FocusedNode); + Results := GridResult(Grid); + Results.RecNo := RowNum^; + try + Results.SetCol(Grid.FocusedColumn-1, '', True, False); + except + on E:EDbError do + ErrorDialog(E.Message); + end; + Grid.RepaintNode(Grid.FocusedNode); + ValidateControls(Sender); +end;} + + +{procedure TMainForm.AnyGridMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; + MousePos: TPoint; var Handled: Boolean); +var + VT: TVirtualStringTree; + Node: PVirtualNode; + NewFontSize: Integer; +begin + VT := Sender as TVirtualStringTree; + if ssAlt in Shift then begin + // Advance to next or previous grid node on Shift+MouseWheel + if Assigned(VT.FocusedNode) then begin + if WheelDelta > 0 then + Node := VT.FocusedNode.PrevSibling + else + Node := VT.FocusedNode.NextSibling; + if Assigned(Node) then begin + SelectNode(VT, Node); + Handled := True; + end; + end; + end else if KeyPressed(VK_CONTROL) then begin + // Change font size with MouseWheel + if AppSettings.ReadBool(asWheelZoom) then begin + NewFontSize := VT.Font.Size; + if WheelDelta > 0 then + Inc(NewFontSize) + else + Dec(NewFontSize); + NewFontSize := Max(NewFontSize, 1); + AppSettings.ResetPath; + AppSettings.WriteInt(asDataFontSize, NewFontSize); + ApplyFontToGrids; + end; + end else if ssShift in Shift then begin + // Horizontal scrolling with Alt+Mousewheel + VT.OffsetX := VT.OffsetX + WheelDelta; + Handled := True; + end; +end;} + + +{** + Content of a grid cell was modified +} +{procedure TMainForm.AnyGridNewText(Sender: TBaseVirtualTree; Node: PVirtualNode; Column: TColumnIndex; NewText: String); +var + Results: TDBQuery; + RowNum: PInt64; + Timestamp: Int64; + IsNull: Boolean; + ResultCol: Integer; +begin + Results := GridResult(Sender); + if not Results.IsEditable then + Exit; + ResultCol := Column - 1; + RowNum := Sender.GetNodeData(Node); + Results.RecNo := RowNum^; + try + if (not FGridEditFunctionMode) and (Results.DataType(ResultCol).Category in [dtcInteger, dtcReal]) then begin + if HandleUnixTimestampColumn(Sender, Column) then begin + Timestamp := DateTimeToUnix(StrToDateTime(NewText)); + Inc(Timestamp, FTimeZoneOffset); + NewText := IntToStr(Timestamp) + end else + NewText := NewText; + end; + FClipboardHasNull := FClipboardHasNull and (Clipboard.TryAsText = ''); + IsNull := FGridPasting and FClipboardHasNull; + Results.SetCol(ResultCol, NewText, IsNull, FGridEditFunctionMode); + except + on E:EDbError do + ErrorDialog(E.Message); + end; + FGridEditFunctionMode := False; + ValidateControls(Sender); +end;} + + +{** + DataGrid: node and/or column focus is about to change. See if we allow that. +} +{procedure TMainForm.AnyGridFocusChanging(Sender: TBaseVirtualTree; OldNode, + NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: + Boolean); +var + Results: TDBQuery; + RowNum: PInt64; +begin + // Detect changed focus and update row + Allowed := True; + Results := GridResult(Sender); + if Assigned(OldNode) and (OldNode <> NewNode) then begin + RowNum := Sender.GetNodeData(OldNode); + Results.RecNo := RowNum^; + if Results.Modified then begin + Allowed := Results.SaveModifications; + DisplayRowCountStats(Sender); + end else if Results.Inserted then begin + if NewNode <> nil then begin + Results.DiscardModifications; + Sender.DeleteNode(OldNode); + end else begin + Allowed := False; + end; + end; + end; +end;} + + +{procedure TMainForm.AnyGridFocusChanged(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex); +begin + ValidateControls(Sender); + if Assigned(Node) and pnlPreview.Visible then + UpdatePreviewPanel; + // Vtree does not focus cell after tab pressing. See issue #3139. + // Most probably a missing thing / bug in TBaseVirtualTree.SetFocusedNodeAndColumn + Sender.ScrollIntoView(Sender.FocusedNode, False, True); + // Required for highlighting fields with same text + Sender.Invalidate; +end;} + + +{procedure TMainForm.AnyGridChange(Sender: TBaseVirtualTree; Node: PVirtualNode); +begin + // Ensure "delete row" button state is valid, see issue #624 + ValidateControls(Sender); + UpdateLineCharPanel; +end;} + + +{procedure TMainForm.AnyGridKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); +var + g: TVirtualStringTree; +begin + g := TVirtualStringTree(Sender); + case Key of + VK_HOME: begin + g.FocusedColumn := g.Header.Columns.GetFirstVisibleColumn(True); + if ssCtrl in Shift then begin + // VT itself focuses the first node since v7.0 + end else + Key := 0; + end; + VK_END: begin + g.FocusedColumn := g.Header.Columns.GetLastVisibleColumn(True); + if ssCtrl in Shift then begin + if g = DataGrid then + actDataShowAll.Execute; + // VT itself focuses the last node since v7.0 + end else begin + Key := 0; + end; + end; + VK_RETURN: if Assigned(g.FocusedNode) then g.EditNode(g.FocusedNode, g.FocusedColumn); + VK_DOWN: if g.FocusedNode = g.GetLast then actDataInsertExecute(actDataInsert); + VK_NEXT: if (g = DataGrid) and (g.FocusedNode = g.GetLast) then actDataShowNext.Execute; + end; +end;} + + +{procedure TMainForm.AnyGridEditing(Sender: TBaseVirtualTree; Node: + PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); +begin + Allowed := False; + try + if not AnyGridEnsureFullRow(Sender as TVirtualStringTree, Node) then + ErrorDialog(_('Could not load full row data.')) + else begin + Allowed := True; + // Move Esc shortcut from "Cancel row editing" to "Cancel cell editing" + actDataCancelChanges.ShortCut := 0; + actDataPostChanges.ShortCut := 0; + end; + except on E:EDbError do + ErrorDialog(_('Grid editing error'), E.Message); + end; +end;} + +{procedure TMainForm.AnyGridEdited(Sender: TBaseVirtualTree; Node: + PVirtualNode; Column: TColumnIndex); +begin + // Reassign Esc to "Cancel row editing" action + if ([tsEditing, tsEditPending] * Sender.TreeStates) = [] then begin + actDataCancelChanges.ShortCut := TextToShortcut('Esc'); + actDataPostChanges.ShortCut := TextToShortcut('Ctrl+Enter'); + end; +end;} + +{procedure TMainForm.AnyGridEditCancelled(Sender: TBaseVirtualTree; Column: + TColumnIndex); +begin + // Reassign Esc to "Cancel row editing" action + actDataCancelChanges.ShortCut := TextToShortcut('Esc'); + actDataPostChanges.ShortCut := TextToShortcut('Ctrl+Enter'); +end;} + +{procedure TMainForm.AnyGridCreateEditor(Sender: TBaseVirtualTree; Node: + PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink); +const + ForeignItemsLimit: Integer = 10000; +var + VT: TVirtualStringTree; + HexEditor: THexEditorLink; + DateTimeEditor: TDateTimeEditorLink; + EnumEditor: TEnumEditorLink; + SetEditor: TSetEditorLink; + InplaceEditor: TInplaceEditorLink; + TypeCat: TDBDatatypeCategoryIndex; + ForeignKey: TForeignKey; + TblColumn: TTableColumn; + idx, MicroSecondsPrecision: Integer; + KeyCol, TextCol, SQL, NowText: String; + Columns: TTableColumnList; + ForeignResults, Results: TDBQuery; + Conn: TDBConnection; + RowNum: PInt64; + RefObj: TDBObject; + AllowEdit, DisplayHex: Boolean; + SQLFunc: TSQLFunction; + ResultCol: Integer; +begin + VT := Sender as TVirtualStringTree; + Results := GridResult(VT); + RowNum := VT.GetNodeData(Node); + Results.RecNo := RowNum^; + ResultCol := Column - 1; + Conn := Results.Connection; + // Allow editing, or leave readonly mode + AllowEdit := Results.IsEditable; + TblColumn := Results.ColAttributes(ResultCol); + + // Find foreign key values + if AppSettings.ReadBool(asForeignDropDown) and (Sender = DataGrid) then begin + for ForeignKey in SelectedTableForeignKeys do begin + idx := ForeignKey.Columns.IndexOf(DataGrid.Header.Columns[Column].Text); + if idx > -1 then try + // Find the first text column if available and use that for displaying in the pulldown instead of using meaningless id numbers + RefObj := ForeignKey.ReferenceTableObj; + if not Assigned(RefObj) then + Continue; + + TextCol := ''; + Columns := RefObj.TableColumns; + for TblColumn in Columns do begin + if (TblColumn.DataType.Category = dtcText) and (TblColumn.Name <> ForeignKey.ForeignColumns[idx]) then begin + TextCol := TblColumn.Name; + break; + end; + end; + + KeyCol := Conn.QuoteIdent(ForeignKey.ForeignColumns[idx]); + if TextCol <> '' then begin + SQL := KeyCol+', ' + Conn.GetSQLSpecifity(spFuncLeft, [Conn.QuoteIdent(TextCol), 256])+ + ' FROM ' + RefObj.QuotedDbAndTableName + + ' GROUP BY '+KeyCol+', '+Conn.QuoteIdent(TextCol)+ // MSSQL complains if the text columns is not grouped + ' ORDER BY '+Conn.QuoteIdent(TextCol); + end else begin + SQL := KeyCol+ + ' FROM ' + RefObj.QuotedDbAndTableName + + ' GROUP BY '+KeyCol+ + ' ORDER BY '+KeyCol; + end; + SQL := Conn.ApplyLimitClause('SELECT', SQL, ForeignItemsLimit, 0); + + ForeignResults := Conn.GetResults(SQL); + if ForeignResults.RecordCount < ForeignItemsLimit then begin + EnumEditor := TEnumEditorLink.Create(VT, AllowEdit, TblColumn); + EditLink := EnumEditor; + DisplayHex := (not actBlobAsText.Checked) and (ForeignResults.DataType(0).Category in [dtcBinary, dtcSpatial]); + while not ForeignResults.Eof do begin + if DisplayHex then + EnumEditor.ValueList.Add(ForeignResults.HexValue(0)) + else + EnumEditor.ValueList.Add(ForeignResults.Col(0)); + if TextCol <> '' then begin + if DisplayHex then + EnumEditor.DisplayList.Add(ForeignResults.HexValue(0)+': '+ForeignResults.Col(1)) + else + EnumEditor.DisplayList.Add(ForeignResults.Col(0)+': '+ForeignResults.Col(1)); + end; + ForeignResults.Next; + end; + end else begin + LogSQL(f_('Connected table has too many rows. Foreign key drop-down is limited to %d items.', [ForeignItemsLimit]), lcInfo); + end; + ForeignResults.Free; + break; + except on E:EDbError do + // Error gets logged, do nothing more here. All other exception types raise please. + end; + end; + end; + + FGridEditFunctionMode := FGridEditFunctionMode or Results.IsFunction(ResultCol); + if FGridEditFunctionMode then begin + EnumEditor := TEnumEditorLink.Create(VT, AllowEdit, TblColumn); + for SQLFunc in Conn.SQLFunctions do + EnumEditor.ValueList.Add(SQLFunc.Name + SQLFunc.Declaration); + EnumEditor.AllowCustomText := True; + EditLink := EnumEditor; + end; + + TypeCat := Results.DataType(ResultCol).Category; + + if Assigned(EditLink) then + // Editor was created above, do nothing now + else if (Results.DataType(ResultCol).Index in [dbdtEnum, dbdtBool]) and AppSettings.ReadBool(asFieldEditorEnum) then begin + EnumEditor := TEnumEditorLink.Create(VT, AllowEdit, TblColumn); + EnumEditor.ValueList := Results.ValueList(ResultCol); + EditLink := EnumEditor; + end else if (TypeCat = dtcText) or ((TypeCat in [dtcBinary, dtcSpatial]) and actBlobAsText.Checked) then begin + InplaceEditor := TInplaceEditorLink.Create(VT, AllowEdit, TblColumn); + InplaceEditor.MaxLength := Results.MaxLength(ResultCol); + InplaceEditor.TitleText := Results.ColumnOrgNames[ResultCol]; + InplaceEditor.ButtonVisible := True; + EditLink := InplaceEditor; + end else if (TypeCat in [dtcBinary, dtcSpatial]) and AppSettings.ReadBool(asFieldEditorBinary) then begin + HexEditor := THexEditorLink.Create(VT, AllowEdit, TblColumn); + HexEditor.MaxLength := Results.MaxLength(ResultCol); + HexEditor.TitleText := Results.ColumnOrgNames[ResultCol]; + EditLink := HexEditor; + end else if (TypeCat = dtcTemporal) + and AppSettings.ReadBool(asFieldEditorDatetime) + and Assigned(TblColumn) // Editor crashes without a column object (on joins), see #1024 + then begin + // Ensure date/time editor starts with a non-empty text value + if (Results.Col(ResultCol) = '') and AppSettings.ReadBool(asFieldEditorDatetimePrefill) then begin + case Results.DataType(ResultCol).Index of + dbdtDate: NowText := DateToStr(Now); + dbdtTime: NowText := TimeToStr(Now); + // Add this case to prevent error with datatype year and sql_mode STRICT_TRANS_TABLES + // who absolutly want year and not date time + // http://www.heidisql.com/forum.php?t=14728 + dbdtYear: NowText := FormatDateTime('yyyy',Now); + else NowText := DateTimeToStr(Now); + end; + MicroSecondsPrecision := MakeInt(Results.ColAttributes(ResultCol).LengthSet); + // Don't generate MicroSecond when DataType is Year + if (MicroSecondsPrecision > 0) and (Results.DataType(ResultCol).Index <> dbdtYear ) then + NowText := NowText + '.' + StringOfChar('0', MicroSecondsPrecision); + VT.Text[Node, Column] := NowText; + end; + DateTimeEditor := TDateTimeEditorLink.Create(VT, AllowEdit, TblColumn); + EditLink := DateTimeEditor; + end else if AppSettings.ReadBool(asFieldEditorDatetime) + and HandleUnixTimestampColumn(Sender, Column) + and Assigned(TblColumn) // see above + then begin + DateTimeEditor := TDateTimeEditorLink.Create(VT, AllowEdit, TblColumn); + EditLink := DateTimeEditor; + end else if (Results.DataType(ResultCol).Index = dbdtSet) and AppSettings.ReadBool(asFieldEditorSet) then begin + SetEditor := TSetEditorLink.Create(VT, AllowEdit, TblColumn); + SetEditor.ValueList := Results.ValueList(ResultCol); + EditLink := SetEditor; + end else begin + InplaceEditor := TInplaceEditorLink.Create(VT, AllowEdit, TblColumn); + InplaceEditor.ButtonVisible := False; + EditLink := InplaceEditor; + end; + Sender.FocusedNode := Node; + Sender.FocusedColumn := Column; +end;} + + +{procedure TMainForm.menuShowSizeColumnClick(Sender: TObject); +var + Item: TMenuItem; +begin + if coVisible in DBtree.Header.Columns[1].Options then + DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options - [coVisible] + else + DBtree.Header.Columns[1].Options := DBtree.Header.Columns[1].Options + [coVisible]; + Item := Sender as TMenuItem; + Item.Checked := coVisible in DBtree.Header.Columns[1].Options; + AppSettings.ResetPath; + AppSettings.WriteBool(asDisplayObjectSizeColumn, Item.Checked); +end;} + + +{procedure TMainForm.menuAlwaysGenerateFilterClick(Sender: TObject); +begin + // Store setting for toggled filter generation + AppSettings.WriteBool(asAlwaysGenerateFilter, menuAlwaysGenerateFilter.Checked); +end;} + +{procedure TMainForm.menuAutoExpandClick(Sender: TObject); +var + Item: TMenuItem; +begin + // Activate expand on click tree feature + if toAutoExpand in DBtree.TreeOptions.AutoOptions then + DBtree.TreeOptions.AutoOptions := DBtree.TreeOptions.AutoOptions - [toAutoExpand] + else + DBtree.TreeOptions.AutoOptions := DBtree.TreeOptions.AutoOptions + [toAutoExpand]; + Item := Sender as TMenuItem; + Item.Checked := toAutoExpand in DBtree.TreeOptions.AutoOptions; + AppSettings.ResetPath; + AppSettings.WriteBool(asAutoExpand, Item.Checked); +end;} + + +{procedure TMainForm.actGroupObjectsExecute(Sender: TObject); +begin + // Group tree objects by type + RefreshTree(nil); +end;} + + +{procedure TMainForm.AutoCalcColWidth(Tree: TVirtualStringTree; Column: TColumnIndex); +var + Node: PVirtualNode; + i, ColTextWidth, ContentTextWidth: Integer; + Rect: TRect; + Col: TVirtualTreeColumn; +begin + // Find optimal default width for columns. Needs to be done late, after the SQL + // composing to enable text width calculation based on actual table content + // Weird: Fixes first time calculation always based on Tahoma/8pt font + Tree.Canvas.Font := Tree.Font; + Col := Tree.Header.Columns[Column]; + if not (coVisible in Col.Options) then + Exit; + ColTextWidth := Tree.Canvas.TextWidth(Col.Text); + // Add space for sort glyph + if Col.ImageIndex > -1 then + ColTextWidth := ColTextWidth + 20; + Node := Tree.GetFirstVisible; + // Go backwards 50 nodes from focused one if tree was scrolled + i := 0; + if Assigned(Tree.FocusedNode) then begin + Node := Tree.FocusedNode; + while Assigned(Node) do begin + inc(i); + if (Node = Tree.GetFirst) or (i > 50) then + break; + Node := Tree.GetPreviousVisible(Node); + end; + end; + i := 0; + while Assigned(Node) do begin + // Note: this causes the node to load, an exception can propagate + // here if the query or connection dies. + Rect := Tree.GetDisplayRect(Node, Column, True, True); + ContentTextWidth := Rect.Right - Rect.Left; + //if vsMultiLine in Node.States then + // ContentTextWidth := Max(ContentTextWidth, Tree.Canvas.TextWidth(Tree.Text[Node, Column])); + ColTextWidth := Max(ColTextWidth, ContentTextWidth); + inc(i); + if i > 100 then break; + // GetDisplayRect may have implicitely taken the node away. + // Strange that Node keeps being assigned though, probably a timing issue. + if Tree.RootNodeCount = 0 then break; + Node := Tree.GetNextVisible(Node); + end; + // text margins and minimal extra space + ColTextWidth := ColTextWidth + Tree.TextMargin*2 + 20; + ColTextWidth := Min(ColTextWidth, AppSettings.ReadInt(asMaxColWidth)); + Col.Width := ColTextWidth; +end;} + + +{procedure TMainForm.AnyGridBeforeCellPaint(Sender: TBaseVirtualTree; + TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + CellPaintMode: TVTCellPaintMode; CellRect: TRect; var ContentRect: TRect); +var + r: TDBQuery; + cl, clNull, clSameData: TColor; + RowNumber: PInt64; + FocusedIsNull, CurrentIsNull: Boolean; + FieldText, FocusedFieldText: String; + VT: TVirtualStringTree; + ResultCol: Integer; +begin + if Column = -1 then + Exit; + ResultCol := Column -1; + + r := GridResult(Sender); + if (r=nil) or (not r.Connection.Active) then begin + // This event (BeforeCellPaint) is the very first one to notice a broken connection + Sender.Enabled := False; + Exit; + end; + + if ResultCol < 0 then begin + if r.Connection.Parameters.SessionColor <> AppSettings.GetDefaultInt(asTreeBackground) then + TargetCanvas.Brush.Color := r.Connection.Parameters.SessionColor + else + TargetCanvas.Brush.Color := clBtnFace; + TargetCanvas.FillRect(CellRect); + Exit; + end; + + VT := Sender as TVirtualStringTree; + RowNumber := Sender.GetNodeData(Node); + r.RecNo := RowNumber^; + + cl := GetAlternatingRowBackground(Node); + + if (vsSelected in Node.States) and (Node = Sender.FocusedNode) and (Column = Sender.FocusedColumn) then begin + // Focused cell + cl := GetThemeColor(clHighlight) + end else if vsSelected in Node.States then begin + // Selected but not focused cell + if VT.Color > ColorAdjustBrightness(clWhite, -29) then + cl := ColorAdjustBrightness(VT.Color, -29) + else + cl := ColorAdjustBrightness(VT.Color, 29); + end else if r.IsNull(ResultCol) then begin + // Cell with NULL value + clNull := AppSettings.ReadInt(asFieldNullBackground); + if clNull <> clNone then + cl := clNull; + end; + if cl <> clNone then begin + TargetCanvas.Brush.Color := cl; + TargetCanvas.FillRect(CellRect); + end; + + // Probably display background color on fields with same text + // Result pointer gets moved to the focused node.. careful! + if (Sender.FocusedNode <> nil) and (Sender.FocusedColumn > 0) then begin + if ((Node <> Sender.FocusedNode) and (Column = Sender.FocusedColumn)) + or ((Node = Sender.FocusedNode) and (Column <> Sender.FocusedColumn)) then begin + clSameData := AppSettings.ReadInt(asHightlightSameTextBackground); + if clSameData <> clNone then begin + FieldText := r.Col(ResultCol); + CurrentIsNull := r.IsNull(ResultCol); + RowNumber := Sender.GetNodeData(Sender.FocusedNode); + r.RecNo := RowNumber^; // moving result cursor + FocusedFieldText := r.Col(Sender.FocusedColumn-1); + FocusedIsNull := r.IsNull(Sender.FocusedColumn-1); + if (CompareText(FieldText, FocusedFieldText) = 0) and (CurrentIsNull = FocusedIsNull) then begin + TargetCanvas.Brush.Color := clSameData; + TargetCanvas.FillRect(CellRect); + end; + end; + end; + end; + +end;} + + +{procedure TMainForm.HandleDataGridAttributes(RefreshingData: Boolean); +var + rx: TRegExpr; + i: Integer; + Sort, KeyName, FocusedCol, CellFocus, Filter: String; + SortItem: TSortItem; +begin + actDataResetSorting.Enabled := False; + // Clear filter, column names and sort structure if gr + if not Assigned(DataGridHiddenColumns) then begin + DataGridHiddenColumns := TStringList.Create; + DataGridHiddenColumns.Delimiter := DELIM; + DataGridHiddenColumns.StrictDelimiter := True; + end; + if not Assigned(DataGridFocusedCell) then + DataGridFocusedCell := TStringList.Create; + // Remember focused node and column for selected table + if Assigned(DataGrid.FocusedNode) + and (ActiveConnection <> nil) + and (DataGridTable <> nil) + and Assigned(DataGridTable) + then begin + try + KeyName := DataGridTable.QuotedDatabase+'.'+DataGridTable.QuotedName; + FocusedCol := ''; + if DataGrid.FocusedColumn > NoColumn then + FocusedCol := DataGrid.Header.Columns[DataGrid.FocusedColumn].Text; + DataGridFocusedCell.Values[KeyName] := IntToStr(DataGrid.FocusedNode.Index) + DELIM + FocusedCol; + except + on E:EAccessViolation do begin + LogSQL('HandleDataGridAttributes: '+E.Message, lcError); + end; + end; + end; + DataGridFocusedNodeIndex := 0; + DataGridFocusedColumnName := ''; + KeyName := ActiveDbObj.QuotedDatabase+'.'+ActiveDbObj.QuotedName; + CellFocus := DataGridFocusedCell.Values[KeyName]; + if CellFocus <> '' then begin + DataGridFocusedNodeIndex := MakeInt(Explode(DELIM, CellFocus)[0]); + DataGridFocusedColumnName := Explode(DELIM, CellFocus)[1]; + end; + if not RefreshingData then begin + DataGridHiddenColumns.Clear; + SynMemoFilter.Clear; + FDataGridSortItems.Clear; + DataGridWantedRowCount := 0; + while DataGridFocusedNodeIndex >= DataGridWantedRowCount do + Inc(DataGridWantedRowCount, AppSettings.ReadInt(asDatagridRowsPerStep)); + end else begin + // Save current attributes if grid gets refreshed + AppSettings.SessionPath := GetRegKeyTable; + if DataGridHiddenColumns.Count > 0 then + AppSettings.WriteString(asHiddenColumns, DataGridHiddenColumns.DelimitedText) + else if AppSettings.ValueExists(asHiddenColumns) then + AppSettings.DeleteValue(asHiddenColumns); + + if SynMemoFilter.GetTextLen > 0 then + AppSettings.WriteString(asFilter, SynMemoFilter.Text) + else if AppSettings.ValueExists(asFilter) then + AppSettings.DeleteValue(asFilter); + + Sort := ''; + for SortItem in FDataGridSortItems do begin + Sort := Sort + IntToStr(Integer(SortItem.Order)) + '_' + SortItem.Column + DELIM; + end; + if Sort <> '' then + AppSettings.WriteString(asSort, Sort) + else if AppSettings.ValueExists(asSort) then + AppSettings.DeleteValue(asSort); + end; + + // Auto remove registry spam if table folder is empty + if AppSettings.SessionPathExists(GetRegKeyTable) then begin + AppSettings.SessionPath := GetRegKeyTable; + if AppSettings.IsEmptyKey then + AppSettings.DeleteCurrentKey; + end; + + // Do nothing if table was not filtered yet + if not AppSettings.SessionPathExists(GetRegKeyTable) then + Exit; + + // Columns + if AppSettings.ValueExists(asHiddenColumns) then + DataGridHiddenColumns.DelimitedText := AppSettings.ReadString(asHiddenColumns); + + // Set filter, without changing cursor position + if AppSettings.ValueExists(asFilter) then begin + Filter := AppSettings.ReadString(asFilter); + if SynMemoFilter.Text <> Filter then begin + SynMemoFilter.Text := Filter; + SynMemoFilter.Modified := True; + end; + if SynMemoFilter.GetTextLen > 0 then + ToggleFilterPanel(True); + end; + + // Sort + if AppSettings.ValueExists(asSort) then begin + FDataGridSortItems.Clear; + rx := TRegExpr.Create; + rx.Expression := '\b(\d)_(.+)\'+DELIM; + rx.ModifierG := False; + if rx.Exec(AppSettings.ReadString(asSort)) then while true do begin + // Check if column exists, could be renamed or deleted + for i:=0 to SelectedTableColumns.Count-1 do begin + if SelectedTableColumns[i].Name = rx.Match[2] then begin + SortItem := FDataGridSortItems.AddNew; + SortItem.Column := rx.Match[2]; + SortItem.Order := TSortItemOrder(StrToIntDef(rx.Match[1], 0)); + LogSQL('Restored sorting for column '+SortItem.Column+'/'+Integer(SortItem.Order).ToString+' in TMainForm.HandleDataGridAttributes', lcDebug); + Break; + end; + end; + if not rx.ExecNext then + break; + end; + actDataResetSorting.Enabled := FDataGridSortItems.Count > 0; + end; + + AppSettings.ResetPath; +end;} + + +{function TMainForm.GetRegKeyTable: String; +begin + // Return the slightly complex registry path to \Servers\CustomFolder\ActiveServer\curdb|curtable + Result := ActiveDbObj.Connection.Parameters.SessionPath + '\' + + ActiveDatabase + DELIM + ActiveDbObj.Name; +end;} + + +{procedure TMainForm.AnyGridMouseUp(Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer); +var + Grid: TVirtualStringTree; + Hit: THitInfo; + Results: TDBQuery; +begin + // Detect mouse hit in grid whitespace and apply changes. + Grid := Sender as TVirtualStringTree; + if not Assigned(Grid.FocusedNode) then + Exit; + Grid.GetHitTestInfoAt(X, Y, False, Hit); + if (Hit.HitNode = nil) or (Hit.HitColumn = NoColumn) or (Hit.HitColumn = InvalidColumn) then begin + Results := GridResult(Grid); + if Results.Modified then begin + Results.SaveModifications; + DisplayRowCountStats(Grid); + end; + end; +end;} + + +{procedure TMainForm.ListDatabasesBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); +var + vt: TVirtualStringTree; + i: Integer; + Conn: TDBConnection; +begin + // Invalidate list of databases, before (re)painting + vt := Sender as TVirtualStringTree; + if vt.Tag = VTREE_LOADED then + Exit; + Conn := ActiveConnection; + Screen.Cursor := crHourglass; + vt.Clear; + if Conn <> nil then begin + if vt.Tag = VTREE_NOTLOADED_PURGECACHE then begin + for i:=0 to Conn.AllDatabases.Count-1 do begin + if Conn.DbObjectsCached(Conn.AllDatabases[i]) then begin + if Conn.AllDatabases[i] = ActiveDatabase then + RefreshTree + else + Conn.GetDBObjects(Conn.AllDatabases[i], True); + end; + end; + end; + vt.RootNodeCount := Conn.AllDatabases.Count; + end; + tabDatabases.Caption := _('Databases') + ' ('+FormatNumber(vt.RootNodeCount)+')'; + vt.Tag := VTREE_LOADED; + Screen.Cursor := crDefault; +end;} + + +{procedure TMainForm.ListDatabasesDblClick(Sender: TObject); +begin + // Select database on doubleclick + // TODO: Have DBObjects bound to ListDatabases, so we can sort nodes without breaking references + if Assigned(ListDatabases.FocusedNode) then + SetActiveDatabase(ListDatabases.Text[ListDatabases.FocusedNode, 0], ActiveConnection); +end;} + + +{procedure TMainForm.ListDatabasesGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); +var + db: String; + Conn: TDBConnection; +begin + // Return icon index for databases. Ghosted if db objects not yet in cache. + if Column <> (Sender as TVirtualStringTree).Header.MainColumn then + Exit; + Conn := ActiveConnection; + db := ListDatabases.Text[Node, 0]; + case Kind of + ikNormal, ikSelected: ImageIndex := ICONINDEX_DB; + ikOverlay: if db = Conn.Database then ImageIndex := ICONINDEX_HIGHLIGHTMARKER; + end; + Ghosted := not Conn.DbObjectsCached(db); +end;} + + +{procedure TMainForm.ListDatabasesGetNodeDataSize(Sender: TBaseVirtualTree; + var NodeDataSize: Integer); +begin + // Tell VirtualTree we're using a simple integer as data + NodeDataSize := SizeOf(Int64); +end;} + + +{procedure TMainForm.ListDatabasesInitNode(Sender: TBaseVirtualTree; ParentNode, Node: PVirtualNode; + var InitialStates: TVirtualNodeInitStates); +var + Idx: PInt; +begin + // Integers mapped to the node's index so nodes can be sorted without losing their database name + Idx := Sender.GetNodeData(Node); + Idx^ := Node.Index; +end;} + + +{procedure TMainForm.ListDatabasesGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); +var + Idx: PInt; + Objects: TDBObjectList; + DBname: String; + Conn: TDBConnection; + + function GetItemCount(ItemType: TListNodeType): String; + var + c: Integer; + o: TDBObject; + begin + if Objects <> nil then begin + c := 0; + for o in Objects do begin + if (ItemType = lntNone) or (o.NodeType = ItemType) then + Inc(c); + end; + Result := FormatNumber(c); + end else + Result := ''; + end; + +begin + // Return text for database columns + Idx := Sender.GetNodeData(Node); + + Conn := ActiveConnection; + if Idx^ < Conn.AllDatabases.Count then begin + DBname := Conn.AllDatabases[Idx^]; + end else begin + // Probably a database were dropped shortly before. + DBname := ''; + end; + if Conn.DbObjectsCached(DBname) then + Objects := Conn.GetDBObjects(DBname) + else + Objects := nil; + CellText := ''; + case Column of + 0: CellText := DBname; + 1: if Assigned(Objects) then CellText := FormatByteNumber(Objects.DataSize); + 2: CellText := GetItemCount(lntNone); + 3: if Assigned(Objects) and (Objects.LastUpdate > 0) then CellText := DateTimeToStr(Objects.LastUpdate); + 4: CellText := GetItemCount(lntTable); + 5: CellText := GetItemCount(lntView); + 6: CellText := GetItemCount(lntFunction); + 7: CellText := GetItemCount(lntProcedure); + 8: CellText := GetItemCount(lntTrigger); + 9: CellText := GetItemCount(lntEvent); + 10: if Assigned(Objects) then CellText := Objects.Collation; + end; + +end;} + + +{procedure TMainForm.HostListBeforePaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas); +var + vt: TVirtualStringTree; + OldOffset: TPoint; + Conn: TDBConnection; + Tab: TTabSheet; + Results, Variables: TDBQuery; + i: Integer; + SelectedCaptions: TStringList; + IS_objects: TDBObjectList; + Obj: TDBObject; + ProcessColumns: TTableColumnList; + Columns, FocusedCaption, CleanTabCaption: String; + Col: TVirtualTreeColumn; + LeaveEnabled: Boolean; + ActiveNetType: String; +begin + // Display server variables + vt := Sender as TVirtualStringTree; + if vt.Tag = VTREE_LOADED then + Exit; + Tab := vt.Parent as TTabSheet; + Conn := ActiveConnection; + + // Status + command statistics only available in MySQL, most others also not available in SQLite + LeaveEnabled := False; + ActiveNetType := _('unknown'); + if Conn <> nil then begin + if vt = ListDatabases then + LeaveEnabled := True + else if vt = ListVariables then + LeaveEnabled := Conn.Parameters.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL] + else if vt = ListStatus then + LeaveEnabled := Conn.Parameters.NetTypeGroup in [ngMySQL] + else if vt = ListProcesses then + LeaveEnabled := Conn.Parameters.NetTypeGroup in [ngMySQL, ngMSSQL, ngPgSQL] + else if vt = ListCommandStats then + LeaveEnabled := Conn.Parameters.NetTypeGroup in [ngMySQL]; + ActiveNetType := Conn.Parameters.NetTypeName(False); + end; + if not LeaveEnabled then begin + vt.Clear; + vt.EmptyListMessage := f_('Not available on %s', [ActiveNetType]); + vt.Tag := VTREE_LOADED; + Exit; + end; + + SelectedCaptions := TStringList.Create; + GetVTSelection(vt, SelectedCaptions, FocusedCaption); + SelectNode(vt, nil); + vt.BeginUpdate; + OldOffset := vt.OffsetXY; + vt.Clear; + Screen.Cursor := crHourglass; + + if Conn <> nil then try + Results := GridResult(vt); + if Results <> nil then + FreeAndNil(Results); + if vt = ListVariables then begin + // Do not use FHostListResults on Variables tab, as we cannot query + // session and global variables in one query + Results := nil; + FreeAndNil(FVariableNames); + FreeAndNil(FSessionVars); + FreeAndNil(FGlobalVars); + FVariableNames := TStringList.Create; + FVariableNames.Duplicates := dupIgnore; + FVariableNames.Sorted := True; + FSessionVars := TStringList.Create; + FGlobalVars := TStringList.Create; + Variables := Conn.GetResults(Conn.GetSQLSpecifity(spSessionVariables)); + while not Variables.Eof do begin + FVariableNames.Add(Variables.Col(0)); + FSessionVars.Values[Variables.Col(0)] := Variables.Col(1); + Variables.Next; + end; + Variables.Free; + Variables := Conn.GetResults(Conn.GetSQLSpecifity(spGlobalVariables)); + while not Variables.Eof do begin + FVariableNames.Add(Variables.Col(0)); + FGlobalVars.Values[Variables.Col(0)] := Variables.Col(1); + Variables.Next; + end; + Variables.Free; + vt.RootNodeCount := FVariableNames.Count; + end else if vt = ListStatus then begin + Results := Conn.GetResults(Conn.GetSQLSpecifity(spGlobalStatus)); + FStatusServerUptime := Conn.ServerUptime; + end else if vt = ListProcesses then begin + case Conn.Parameters.NetTypeGroup of + ngMySQL: begin + if Conn.InformationSchemaObjects.IndexOf('PROCESSLIST') > -1 then begin + // Minimize network traffic on newer servers by fetching only first KB of SQL query in "Info" column + Columns := Conn.QuoteIdent('ID')+', '+ + Conn.QuoteIdent('USER')+', '+ + Conn.QuoteIdent('HOST')+', '+ + Conn.QuoteIdent('DB')+', '+ + Conn.QuoteIdent('COMMAND')+', '+ + Conn.QuoteIdent('TIME')+', '+ + Conn.QuoteIdent('STATE')+', '+ + 'LEFT('+Conn.QuoteIdent('INFO')+', '+IntToStr(SIZE_KB*50)+') AS '+Conn.QuoteIdent('Info'); + // Get additional column names into SELECT query and ListProcesses tree + IS_objects := Conn.GetDBObjects(Conn.InfSch); + for Obj in IS_objects do begin + if Obj.Name = 'PROCESSLIST' then begin + ProcessColumns := Obj.TableColumns; + for i:=8 to ProcessColumns.Count-1 do begin + Columns := Columns + ', '+Conn.QuoteIdent(ProcessColumns[i].Name); + if ListProcesses.Header.Columns.Count <= i then + Col := ListProcesses.Header.Columns.Add + else + Col := ListProcesses.Header.Columns[i]; + Col.Options := Col.Options + [coVisible]; + Col.Text := ProcessColumns[i].Name; + end; + // Hide unused tree columns + for i:=ListProcesses.Header.Columns.Count-1 downto ProcessColumns.Count do + ListProcesses.Header.Columns[i].Options := ListProcesses.Header.Columns[i].Options - [coVisible]; + ProcessColumns.Free; + break; + end; + end; + Results := Conn.GetResults('SELECT '+Columns+' FROM '+ + Conn.QuoteIdent(Conn.InfSch)+'.'+Conn.QuoteIdent('PROCESSLIST')); + end else begin + // Older servers fetch the whole query length, but at least we cut them off below, so a high memory usage is just a peak + Results := Conn.GetResults('SHOW FULL PROCESSLIST'); + end; + end; + ngMSSQL: begin + Results := Conn.GetResults('SELECT '+ + Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('spid')+ + ', RTRIM('+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('loginame')+') AS '+Conn.QuoteIdent('loginname')+ + ', RTRIM('+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('hostname')+') AS '+Conn.QuoteIdent('hostname')+ + ', '+Conn.QuoteIdent('d')+'.'+Conn.QuoteIdent('name')+ + ', '+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('cmd')+ + ', '+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('waittime')+ + ', RTRIM('+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('status')+'), '+ + 'NULL AS '+Conn.QuoteIdent('Info')+' '+ + 'FROM '+Conn.QuoteIdent('sys')+'.'+Conn.QuoteIdent('sysprocesses')+' AS '+Conn.QuoteIdent('p')+ + ', '+Conn.GetSQLSpecifity(spDatabaseTable)+' AS '+Conn.QuoteIdent('d')+ + ' WHERE '+Conn.QuoteIdent('p')+'.'+Conn.QuoteIdent('dbid')+'='+Conn.QuoteIdent('d')+'.'+Conn.GetSQLSpecifity(spDatabaseTableId) + ); + end; + ngPgSQL: begin + Results := Conn.GetResults('SELECT '+ + Conn.QuoteIdent('pid')+ + ', '+Conn.QuoteIdent('usename')+ + ', '+Conn.QuoteIdent('client_addr')+ + ', '+Conn.QuoteIdent('datname')+ + ', application_name '+ + ', EXTRACT(EPOCH FROM CURRENT_TIMESTAMP - '+Conn.QuoteIdent('query_start')+')::INTEGER'+ + ', '+Conn.QuoteIdent('state')+ + ', '+Conn.QuoteIdent('query')+ + ' FROM '+Conn.QuoteIdent('pg_stat_activity') + ); + end; + else begin + raise Exception.CreateFmt(_(MsgUnhandledNetType), [Integer(Conn.Parameters.NetType)]); + end; + end; + FProcessListMaxTime := 1; + for i:=0 to Results.RecordCount-1 do begin + FProcessListMaxTime := Max(FProcessListMaxTime, MakeInt(Results.Col(5))); + Results.Next; + end; + end else if vt = ListCommandStats then begin + Results := Conn.GetResults(Conn.GetSQLSpecifity(spCommandsCounters)); + FCommandStatsServerUptime := Conn.ServerUptime; + FCommandStatsQueryCount := 0; + while not Results.Eof do begin + Inc(FCommandStatsQueryCount, MakeInt(Results.Col(1))); + Results.Next; + end; + end; + + FHostListResults[Tab.PageIndex] := Results; + if Results <> nil then + vt.RootNodeCount := Results.RecordCount; + vt.OffsetXY := OldOffset; + except + on E:Exception do ErrorDialog(E.Message); + end; + + Screen.Cursor := crDefault; + // Apply or reset filter + editFilterVTChange(Sender); + vt.EndUpdate; + vt.Tag := VTREE_LOADED; + // Display number of listed values on tab + CleanTabCaption := RegExprGetMatch('^([^\(]+)', Tab.Caption, 1); + Tab.Caption := CleanTabCaption.Trim + ' (' + IntToStr(vt.RootNodeCount) + ')'; + // Restore selection + SetVTSelection(vt, SelectedCaptions, FocusedCaption); +end;} + + +{procedure TMainForm.HostListBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + var ContentRect: TRect); +var + vt: TVirtualStringTree; + Val, Max: Extended; + LoopNode: PVirtualNode; + SessionVal, GlobalVal: String; +begin + PaintAlternatingRowBackground(TargetCanvas, Node, CellRect); + vt := Sender as TVirtualStringTree; + + if (Column in [1,2,4..9]) and (vt = ListDatabases) then begin + // Find out maximum value in column + LoopNode := vt.GetFirst; + Max := 1; + while Assigned(LoopNode) do begin + Val := MakeFloat(vt.Text[LoopNode, Column]); + if Val > Max then + Max := Val; + LoopNode := vt.GetNext(LoopNode); + end; + PaintColorBar(MakeFloat(vt.Text[Node, Column]), Max, TargetCanvas, CellRect); + end; + + // Highlight cell if session variable is different to global variable + if (Column = 1) and (vt = ListVariables) then begin + SessionVal := vt.Text[Node, 1]; + GlobalVal := vt.Text[Node, 2]; + if SessionVal <> GlobalVal then begin + TargetCanvas.Brush.Color := clWebBlanchedAlmond; + TargetCanvas.Pen.Color := TargetCanvas.Brush.Color; + TargetCanvas.Rectangle(CellRect); + end; + end; + + // Nothing special on Status tab + + if (Column = 5) and (vt = ListProcesses) then begin + PaintColorBar(MakeFloat(vt.Text[Node, Column]), FProcessListMaxTime, TargetCanvas, CellRect); + end; + + if (Column = 4) and (vt = ListCommandStats) then begin + // Only paint bar in percentage column + PaintColorBar(MakeFloat(vt.Text[Node, Column]), 100, TargetCanvas, CellRect); + end; +end;} + + +{procedure TMainForm.actFollowForeignKeyExecute(Sender: TObject); +var + Results: TDBQuery; + RowNum: PInt64; + FocusedColumnName, ForeignColumnName, ReferenceTable: String; + ForeignKey: TForeignKey; + i: Integer; + DBObj: TDBObject; + Datatype: TDBDatatype; + DbObjects: TDBObjectList; + Filter: String; + Conn: TDBConnection; +begin + Results := GridResult(DataGrid); + RowNum := DataGrid.GetNodeData(DataGrid.FocusedNode); + Results.RecNo := RowNum^; + FocusedColumnName := Results.ColumnOrgNames[DataGrid.FocusedColumn-1]; + Conn := Results.Connection; + + // find foreign key for current column + for ForeignKey in ActiveDBObj.TableForeignKeys do begin + i := ForeignKey.Columns.IndexOf(FocusedColumnName); + if i > -1 then begin + ForeignColumnName := ForeignKey.ForeignColumns[i]; + ReferenceTable := ForeignKey.ReferenceTable; + break; + end; + end; + if ForeignColumnName = '' then begin + LogSQL(f_('Foreign key not found for column "%s"', [FocusedColumnName]), lcInfo); + Exit; + end; + Datatype := Results.DataType(DataGrid.FocusedColumn-1); + // filter to show only rows linked by the foreign key + if DataType.Category in [dtcBinary, dtcSpatial] then + Filter := Conn.QuoteIdent(ForeignColumnName)+'='+Results.HexValue(DataGrid.FocusedColumn-1) + else + Filter := Conn.QuoteIdent(ForeignColumnName)+'='+Conn.EscapeString(Results.Col(DataGrid.FocusedColumn-1)); + + // Jumping to ReferenceTable. Caution, this invalidates the above used Results + DbObjects := Conn.GetDBObjects(ActiveDatabase); + for DBObj in DbObjects do begin + if DBObj.Database + '.' + DBObj.Name = ReferenceTable then begin + ActiveDBObj := DBObj; + Break; + end; + end; + + SynMemoFilter.Text := Filter; + ToggleFilterPanel(True); + actApplyFilter.Execute; + // SynMemoFilter will be cleared and set value of asFilter (in HandleDataGridAttributes from DataGridBeforePaint) + AppSettings.SessionPath := GetRegKeyTable; + AppSettings.WriteString(asFilter, Filter); +end;} + + +{procedure TMainForm.actCopyGridNodesExecute(Sender: TObject); +var + SenderControl: TComponent; + SenderName: String; + Grid: TVirtualStringTree; + Header, Body, Line, Data: String; + Separator, Encloser, Terminator: String; + Node: PVirtualNode; + Col: TColumnIndex; + Indent, NodesCopied: Integer; + IsFirstCol: Boolean; +begin + // Copy tree nodes as CSV, from any VirtualTree, not only from data or result grids + // See issue #2083 + SenderControl := PopupComponent(Sender); + if SenderControl=nil then + SenderControl := Screen.ActiveControl; + + if not (SenderControl is TVirtualStringTree) then begin + if SenderControl=nil then + SenderName := 'nil' + else + SenderName := SenderControl.Name; + ErrorDialog(f_('No listing or tree focused. ActiveControl is %s', [SenderName])); + Exit; + end; + + Screen.Cursor := crHourGlass; + Grid := TVirtualStringTree(SenderControl); + // Grid.CopyToClipboard; // Does nothing (?) + + Separator := #9; + Encloser := ''; + Terminator := SLineBreak; + + Header := ''; + Col := Grid.Header.Columns.GetFirstVisibleColumn(True); + IsFirstCol := True; + while Col > NoColumn do begin + Data := Grid.Header.Columns[Col].Text; + //Data := StringReplace(Data, Encloser, Encloser+Encloser, [rfReplaceAll]); + if not IsFirstCol then + Header := Header + Separator; + IsFirstCol := False; + //Header := Header + Encloser + Data + Encloser; + Header := Header + Data; + Col := Grid.Header.Columns.GetNextVisibleColumn(Col); + end; + Header := Header + Terminator; + + Body := ''; + NodesCopied := 0; + Node := Grid.GetFirstInitialized; + while Assigned(Node) do begin + if Grid.IsVisible[Node] then begin + IsFirstCol := True; + Line := ''; + + // One empty cell for each indentation level + for Indent := 1 to Grid.GetNodeLevel(Node) do begin + if not IsFirstCol then + Line := Line + Separator; + IsFirstCol := False; + //Line := Line + Encloser + Encloser; + end; + + // Add data cells + Col := Grid.Header.Columns.GetFirstVisibleColumn(True); + while Col > NoColumn do begin + Data := Grid.Text[Node, Col]; + //Data := StringReplace(Data, Encloser, Encloser+Encloser, [rfReplaceAll]); + if not IsFirstCol then + Line := Line + Separator; + IsFirstCol := False; + //Line := Line + Encloser + Data + Encloser; + Line := Line + Data; + Col := Grid.Header.Columns.GetNextVisibleColumn(Col); + end; + + Body := Body + Line + Terminator; + Inc(NodesCopied); + end; + Node := Grid.GetNextInitialized(Node); + end; + + Clipboard.TryAsText := Header + Body; + + Screen.Cursor := crDefault; + LogSQL(f_('%s: %s lines copied to clipboard', [SLogPrefixInfo, FormatNumber(NodesCopied)]), lcInfo); + MessageBeep(MB_ICONASTERISK); +end;} + +{procedure TMainForm.actCopyOrCutExecute(Sender: TObject); +var + CurrentControl: TWinControl; + SendingControl: TComponent; + SenderName, TextCopy: String; + Edit: TCustomEdit; + Combo: TCustomComboBox; + Grid: TVirtualStringTree; + SynMemo: TSynMemo; + DoCut, DoCopyRows: Boolean; + IsResultGrid, HasNulls: Boolean; + ClpFormat: Word; + ClpData: THandle; + APalette: HPalette; + Exporter: TSynExporterRTF; + Results: TDBQuery; + RowNum: PInt64; + ExportDialog: TfrmExportGrid; +begin + // Copy text from a focused control to clipboard + CurrentControl := Screen.ActiveControl; + SendingControl := TAction(Sender).ActionComponent; + SenderName := TAction(Sender).Name; + DoCut := Sender = actCut; + DoCopyRows := SenderName.StartsWith(TfrmExportGrid.CopyAsActionPrefix); + FClipboardHasNull := False; + Screen.Cursor := crHourglass; + try + if SendingControl = btnPreviewCopy then begin + if (imgPreview.Picture.Graphic <> nil) and (not imgPreview.Picture.Graphic.Empty) then begin + imgPreview.Picture.SaveToClipBoardFormat(ClpFormat, ClpData, APalette); + ClipBoard.SetAsHandle(ClpFormat, ClpData); + end; + end else if CurrentControl is TCustomEdit then begin + Edit := TCustomEdit(CurrentControl); + if Edit.SelLength > 0 then begin + if DoCut then Edit.CutToClipboard + else Edit.CopyToClipboard; + end; + end else if CurrentControl is TCustomComboBox then begin + Combo := TCustomComboBox(CurrentControl); + if Combo.SelLength > 0 then begin + Clipboard.TryAsText := Combo.SelText; + if DoCut then Combo.SelText := ''; + end; + end else if CurrentControl is TVirtualStringTree then begin + Grid := CurrentControl as TVirtualStringTree; + if Assigned(Grid.FocusedNode) then begin + IsResultGrid := Grid = ActiveGrid; + FGridCopying := True; + if IsResultGrid then begin + if DoCopyRows then begin + ExportDialog := TfrmExportGrid.Create(Sender as TAction); + ExportDialog.Grid := Grid; + ExportDialog.btnOK.Click; + ExportDialog.Free; + end else begin + // Handle NULL values in grids, see issue #3171 + AnyGridEnsureFullRow(Grid, Grid.FocusedNode); + Results := GridResult(Grid); + RowNum := Grid.GetNodeData(Grid.FocusedNode); + Results.RecNo := RowNum^; + if Results.IsNull(Grid.FocusedColumn-1) then begin + Clipboard.TryAsText := ''; + FClipboardHasNull := True; + end else begin + TextCopy := Grid.Text[Grid.FocusedNode, Grid.FocusedColumn]; + RemoveNullChars(TextCopy, HasNulls); + Clipboard.TryAsText := TextCopy; + end; + if DoCut then + Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := ''; + end; + end else begin + TextCopy := Grid.Text[Grid.FocusedNode, Grid.FocusedColumn]; + RemoveNullChars(TextCopy, HasNulls); + Clipboard.TryAsText := TextCopy; + end; + FGridCopying := False; + end; + end else if CurrentControl is TSynMemo then begin + SynMemo := CurrentControl as TSynMemo; + if SynMemo.SelAvail then begin + // Create both text and RTF clipboard format, so rich text applications can paste highlighted SQL + Clipboard.Open; + Clipboard.TryAsText := SynMemo.SelText; + Exporter := TSynExporterRTF.Create(Self); + Exporter.Highlighter := SynMemo.Highlighter; + Exporter.ExportAll(Explode(CRLF, SynMemo.SelText)); + if DoCut then SynMemo.CutToClipboard + else SynMemo.CopyToClipboard; + Exporter.CopyToClipboard; + Clipboard.Close; + Exporter.Free; + end; + end else begin + raise Exception.Create('Unhandled control in clipboard action: '+IfThen(Assigned(CurrentControl), CurrentControl.Name, 'nil')); + end; + except + on E:Exception do begin + LogSQL(E.ClassName + ': ' + E.Message); + MessageBeep(MB_ICONASTERISK); + end; + end; + Screen.Cursor := crDefault; +end;} + + +{procedure TMainForm.actPasteExecute(Sender: TObject); +var + Control: TWinControl; + Edit: TCustomEdit; + Combo: TComboBox; + Grid: TVirtualStringTree; + SynMemo: TSynMemo; + Success: Boolean; +begin + // Paste text into the focused control + Success := False; + Control := Screen.ActiveControl; + if not Clipboard.HasFormat(CF_TEXT) then begin + // Do nothing, we cannot paste a picture or so + end else if Control is TCustomEdit then begin + Edit := TCustomEdit(Control); + if not Edit.ReadOnly then begin + Edit.PasteFromClipboard; + Success := True; + end; + end else if Control is TComboBox then begin + Combo := TComboBox(Control); + if Combo.Style = csDropDown then begin + Combo.SelText := Clipboard.TryAsText; + Success := True; + end; + end else if Control is TVirtualStringTree then begin + Grid := Control as TVirtualStringTree; + if Assigned(Grid.FocusedNode) and (Grid = ActiveGrid) then begin + FGridPasting := True; + Grid.Text[Grid.FocusedNode, Grid.FocusedColumn] := Clipboard.TryAsText; + Success := True; + FGridPasting := False; + end; + end else if Control is TSynMemo then begin + SynMemo := TSynMemo(Control); + if not SynMemo.ReadOnly then begin + try + SynMemo.PasteFromClipboard; + Success := True; + SynMemo.Modified := True; + except on E:Exception do + ErrorDialog(E.Message); + end; + end; + end; + if not Success then + MessageBeep(MB_ICONASTERISK); +end;} + + +{procedure TMainForm.actSequalSuggestExecute(Sender: TObject); +var + SequalSuggestForm: TSequalSuggestForm; +begin + // Show Sequal Suggest dialog + SequalSuggestForm := TSequalSuggestForm.Create(Self); + SequalSuggestForm.ShowModal; +end;} + +{procedure TMainForm.actSelectAllExecute(Sender: TObject); +var + Control: TWinControl; + Grid: TVirtualStringTree; + ListBox: TListBox; + Success: Boolean; +begin + // Select all items, text or whatever + Success := False; + Control := Screen.ActiveControl; + if Control is TCustomEdit then begin + TCustomEdit(Control).SelectAll; + Success := True; + end else if Control is TVirtualStringTree then begin + Grid := TVirtualStringTree(Control); + if toMultiSelect in Grid.TreeOptions.SelectionOptions then begin + Grid.SelectAll(False); + Success := True; + end; + end else if Control is TSynMemo then begin + TSynMemo(Control).SelectAll; + Success := True; + end else if Control is TListBox then begin + ListBox := TListBox(Control); + if ListBox.MultiSelect then begin + ListBox.SelectAll; + Success := True; + end; + end; + if not Success then + MessageBeep(MB_ICONASTERISK); +end;} + + +{procedure TMainForm.actSelectInverseExecute(Sender: TObject); +var + Control: TWinControl; + Grid: TVirtualStringTree; + ListBox: TListBox; + Success: Boolean; + i: Integer; +begin + // Invert selection in grids or listboxes + Success := False; + Control := Screen.ActiveControl; + if Control is TVirtualStringTree then begin + Grid := TVirtualStringTree(Control); + if toMultiSelect in Grid.TreeOptions.SelectionOptions then begin + Grid.InvertSelection(False); + Success := True; + end; + end else if Control is TListBox then begin + ListBox := TListBox(Control); + if ListBox.MultiSelect then begin + for i:=0 to ListBox.Count-1 do + ListBox.Selected[i] := not ListBox.Selected[i]; + Success := True; + end; + end; + if not Success then + MessageBeep(MB_ICONASTERISK); +end;} + + +{procedure TMainForm.EnumerateRecentFilters; +var + i: Integer; + item: TMenuItem; + rx: TRegExpr; + capt, Path: String; +begin + // Reset menu and combobox + menuRecentFilters.Enabled := False; + for i := menuRecentFilters.Count - 1 downto 0 do + menuRecentFilters.Delete(i); + comboRecentFilters.Items.Clear; + // Enumerate recent filters from registry + Path := GetRegKeyTable+'\'+REGKEY_RECENTFILTERS; + if AppSettings.SessionPathExists(Path) then begin + AppSettings.SessionPath := Path; + rx := TRegExpr.Create; + rx.Expression := '\s+'; + for i:=1 to 20 do begin + // Previously introduced bugs stored some other settings here, see issue #2127 + item := TMenuItem.Create(popupFilter); + capt := AppSettings.ReadString(asRecentFilter, IntToStr(i)); + if capt.IsEmpty then + Break; + capt := rx.Replace(capt, ' ', True); + item.Hint := capt; + item.Caption := StrEllipsis(capt, 50); + item.Tag := i; + item.OnClick := LoadRecentFilter; + menuRecentFilters.Add(item); + comboRecentFilters.Items.Add(capt); + end; + FreeAndNil(rx); + AppSettings.ResetPath; + menuRecentFilters.Enabled := menuRecentFilters.Count > 0; + end; + comboRecentFilters.Visible := comboRecentFilters.Items.Count > 0; + lblRecentFilters.Visible := comboRecentFilters.Visible; + SynMemoFilter.Height := pnlFilter.Height - 3; + SynMemoFilter.Top := comboRecentFilters.Top; + if comboRecentFilters.Visible then begin + SynMemoFilter.Height := SynMemoFilter.Height - comboRecentFilters.Height; + SynMemoFilter.Top := SynMemoFilter.Top + comboRecentFilters.Height; + comboRecentFilters.ItemIndex := 0; + end; +end;} + + +{procedure TMainForm.LoadRecentFilter(Sender: TObject); +var + key: Integer; + Path: String; +begin + // Event handler for both dynamic popup menu items and filter combobox + if Sender is TMenuItem then + key := (Sender as TMenuItem).Tag + else + key := (Sender as TComboBox).ItemIndex+1; + Path := GetRegKeyTable+'\'+REGKEY_RECENTFILTERS; + if AppSettings.SessionPathExists(Path) then begin + AppSettings.SessionPath := Path; + SynMemoFilter.UndoList.AddGroupBreak; + SynMemoFilter.BeginUpdate; + SynMemoFilter.SelectAll; + SynMemoFilter.SelText := AppSettings.ReadString(asRecentFilter, IntToStr(key)); + SynMemoFilter.EndUpdate; + AppSettings.ResetPath; + end; +end;} + + +{procedure TMainForm.PlaceObjectEditor(Obj: TDBObject); +var + EditorClass: TDBObjectEditorClass; +begin + // Place the relevant editor frame onto the editor tab, hide all others + if FTreeRefreshInProgress and Assigned(ActiveObjectEditor) then begin + ActiveObjectEditor.Init(Obj); + UpdateFilterPanel(Self); + end else begin + if Assigned(ActiveObjectEditor) and (Obj.NodeType <> ActiveObjectEditor.DBObject.NodeType) then + FreeAndNil(ActiveObjectEditor); + case Obj.NodeType of + lntTable: EditorClass := TfrmTableEditor; + lntView: EditorClass := TfrmView; + lntProcedure, lntFunction: EditorClass := TfrmRoutineEditor; + lntTrigger: EditorClass := TfrmTriggerEditor; + lntEvent: EditorClass := TfrmEventEditor; + else Exit; + end; + if not Assigned(ActiveObjectEditor) then begin + ActiveObjectEditor := EditorClass.Create(tabEditor); + ActiveObjectEditor.Parent := tabEditor; + SetupSynEditors(ActiveObjectEditor); + end; + ActiveObjectEditor.Init(Obj); + buttonedEditClear(editFilterVT); + end; +end;} + + +{procedure TMainForm.UpdateEditorTab; +var + Cap: String; +begin + tabEditor.ImageIndex := ActiveObjectEditor.DBObject.ImageIndex; + // Reset to grayscale if in background: + PageControlTabHighlight(PageControlMain); + Cap := _(ActiveObjectEditor.DBObject.ObjType)+': '; + if ActiveObjectEditor.DBObject.Name = '' then + Cap := Cap + '['+_('Untitled')+']' + else + Cap := Cap + ActiveObjectEditor.DBObject.Name; + SetTabCaption(tabEditor.PageIndex, Cap); +end;} + + +{procedure TMainForm.menuEditObjectClick(Sender: TObject); +var + Obj: PDBObject; +begin + if ListTables.Focused then begin + // Got here from ListTables.OnDblClick or ListTables's context menu item "Edit" + Obj := ListTables.GetNodeData(ListTables.FocusedNode); + if not Obj.IsSameAs(ActiveDbObj) then + ActiveDBObj := Obj^; + SetMainTab(tabEditor); + end else begin + Obj := DBtree.GetNodeData(DBtree.FocusedNode); + case Obj.NodeType of + lntDb: begin + FCreateDatabaseDialog := TCreateDatabaseForm.Create(Self); + FCreateDatabaseDialog.modifyDB := ActiveDatabase; + if FCreateDatabaseDialog.ShowModal = mrOk then + RefreshTree; + FreeAndNil(FCreateDatabaseDialog); + end; + lntTable..lntEvent: + SetMainTab(tabEditor); + end; + end; +end;} + + +{procedure TMainForm.ListTablesKeyPress(Sender: TObject; var Key: Char); +begin + // Open object editor on pressing Enter + if Ord(Key) = VK_RETURN then + ListTables.OnDblClick(Sender); +end;} + + +{procedure TMainForm.ListTablesDblClick(Sender: TObject); +var + Obj: PDBObject; + vt: TVirtualStringTree; +begin + // DoubleClick: Display editor + vt := Sender as TVirtualStringTree; + if Assigned(vt.FocusedNode) then begin + Obj := vt.GetNodeData(vt.FocusedNode); + ActiveDBObj := Obj^; + // Normally the editor tab is active now, but not when same node was focused before + SetMainTab(tabEditor); + end; +end;} + + +{procedure TMainForm.actNewQueryTabExecute(Sender: TObject); +var + i: Integer; + QueryTab, OldTab: TQueryTab; + HelperColumn: TVirtualTreeColumn; +begin + i := QueryTabs[QueryTabs.Count-1].Number + 1; + OldTab := QueryTabs.ActiveTab; + + QueryTabs.Add(TQueryTab.Create(Self)); + QueryTab := QueryTabs[QueryTabs.Count-1]; + QueryTab.Number := i; + QueryTab.Uid := TQueryTab.GenerateUid; + + QueryTab.TabSheet := TTabSheet.Create(PageControlMain); + QueryTab.TabSheet.Name := tabQuery.Name + i.ToString; + QueryTab.TabSheet.PageControl := PageControlMain; + QueryTab.TabSheet.ImageIndex := tabQuery.ImageIndex; + + QueryTab.CloseButton := TSpeedButton.Create(QueryTab.TabSheet); + QueryTab.CloseButton.Parent := PageControlMain; + QueryTab.CloseButton.Width := 16; + QueryTab.CloseButton.Height := 16; + QueryTab.CloseButton.Flat := True; + VirtualImageListMain.GetBitmap(134, QueryTab.CloseButton.Glyph); + QueryTab.CloseButton.OnMouseDown := CloseButtonOnMouseDown; + QueryTab.CloseButton.OnMouseUp := CloseButtonOnMouseUp; + SetTabCaption(QueryTab.TabSheet.PageIndex, ''); + + // Dumb code which replicates all controls from tabQuery + QueryTab.pnlMemo := TPanel.Create(QueryTab.TabSheet); + QueryTab.pnlMemo.Name := pnlQueryMemo.Name + i.ToString; + QueryTab.pnlMemo.Parent := QueryTab.TabSheet; + QueryTab.pnlMemo.BevelOuter := pnlQueryMemo.BevelOuter; + QueryTab.pnlMemo.Align := pnlQueryMemo.Align; + if Assigned(OldTab) then + QueryTab.pnlMemo.Height := OldTab.pnlMemo.Height + else + QueryTab.pnlMemo.Height := AppSettings.GetDefaultInt(asQuerymemoheight); + QueryTab.pnlMemo.Constraints := pnlQueryMemo.Constraints; + + QueryTab.Memo := TSynMemo.Create(QueryTab.pnlMemo); + QueryTab.Memo.Name := SynMemoQuery.Name + i.ToString; + QueryTab.Memo.Text := ''; + QueryTab.Memo.Parent := QueryTab.pnlMemo; + QueryTab.Memo.Align := SynMemoQuery.Align; + QueryTab.Memo.Constraints := SynMemoQuery.Constraints; + QueryTab.Memo.HintMode := SynMemoQuery.HintMode; + QueryTab.Memo.Left := SynMemoQuery.Left; + QueryTab.Memo.Options := SynMemoQuery.Options; + QueryTab.Memo.PopupMenu := SynMemoQuery.PopupMenu; + QueryTab.Memo.TabWidth := SynMemoQuery.TabWidth; + QueryTab.Memo.RightEdge := SynMemoQuery.RightEdge; + QueryTab.Memo.WantTabs := SynMemoQuery.WantTabs; + QueryTab.Memo.Highlighter := SynMemoQuery.Highlighter; + QueryTab.Memo.Gutter.Assign(SynMemoQuery.Gutter); + QueryTab.Memo.Font.Assign(SynMemoQuery.Font); + QueryTab.Memo.ActiveLineColor := SynMemoQuery.ActiveLineColor; + QueryTab.Memo.OnStatusChange := SynMemoQuery.OnStatusChange; + QueryTab.Memo.OnSpecialLineColors := SynMemoQuery.OnSpecialLineColors; + QueryTab.Memo.OnDragDrop := SynMemoQuery.OnDragDrop; + QueryTab.Memo.OnDragOver := SynMemoQuery.OnDragOver; + QueryTab.Memo.OnDropFiles := SynMemoQuery.OnDropFiles; + QueryTab.Memo.OnKeyPress := SynMemoQuery.OnKeyPress; + QueryTab.Memo.OnMouseWheel := SynMemoQuery.OnMouseWheel; + QueryTab.Memo.OnReplaceText := SynMemoQuery.OnReplaceText; + QueryTab.Memo.OnPaintTransient := SynMemoQuery.OnPaintTransient; + QueryTab.Memo.OnTokenHint := SynMemoQuery.OnTokenHint; + QueryTab.MemoLineBreaks := TLineBreaks(AppSettings.ReadInt(asLineBreakStyle)); + SynCompletionProposal.AddEditor(QueryTab.Memo); + + QueryTab.spltHelpers := TSplitter.Create(QueryTab.pnlMemo); + QueryTab.spltHelpers.Parent := QueryTab.pnlMemo; + QueryTab.spltHelpers.Align := spltQueryHelpers.Align; + QueryTab.spltHelpers.Left := spltQueryHelpers.Left; + QueryTab.spltHelpers.Cursor := spltQueryHelpers.Cursor; + QueryTab.spltHelpers.ResizeStyle := spltQueryHelpers.ResizeStyle; + QueryTab.spltHelpers.Width := spltQueryHelpers.Width; + + QueryTab.pnlHelpers := TPanel.Create(QueryTab.pnlMemo); + QueryTab.pnlHelpers.Name := pnlQueryHelpers.Name + i.ToString; + QueryTab.pnlHelpers.Parent := QueryTab.pnlMemo; + QueryTab.pnlHelpers.Align := pnlQueryHelpers.Align; + QueryTab.pnlHelpers.Constraints := pnlQueryHelpers.Constraints; + QueryTab.pnlHelpers.BevelOuter := pnlQueryHelpers.BevelOuter; + QueryTab.pnlHelpers.Left := pnlQueryHelpers.Left; + if Assigned(OldTab) then + QueryTab.pnlHelpers.Width := OldTab.pnlHelpers.Width + else + QueryTab.pnlHelpers.Width := AppSettings.GetDefaultInt(asQueryhelperswidth); + + QueryTab.filterHelpers := TButtonedEdit.Create(QueryTab.pnlHelpers); + QueryTab.filterHelpers.Name := filterQueryHelpers.Name + i.ToString; + QueryTab.filterHelpers.Text := ''; + QueryTab.filterHelpers.Parent := QueryTab.pnlHelpers; + QueryTab.filterHelpers.Align := filterQueryHelpers.Align; + QueryTab.filterHelpers.TextHint := filterQueryHelpers.TextHint; + QueryTab.filterHelpers.Images := filterQueryHelpers.Images; + QueryTab.filterHelpers.LeftButton.Visible := filterQueryHelpers.LeftButton.Visible; + QueryTab.filterHelpers.LeftButton.ImageIndex := filterQueryHelpers.LeftButton.ImageIndex; + QueryTab.filterHelpers.RightButton.Visible := filterQueryHelpers.RightButton.Visible; + QueryTab.filterHelpers.RightButton.ImageIndex := filterQueryHelpers.RightButton.ImageIndex; + QueryTab.filterHelpers.OnChange := filterQueryHelpers.OnChange; + QueryTab.filterHelpers.OnRightButtonClick := filterQueryHelpers.OnRightButtonClick; + + QueryTab.treeHelpers := TVirtualStringTree.Create(QueryTab.pnlHelpers); + QueryTab.treeHelpers.Name := treeQueryHelpers.Name + i.ToString; + QueryTab.treeHelpers.Parent := QueryTab.pnlHelpers; + QueryTab.treeHelpers.Align := treeQueryHelpers.Align; + QueryTab.treeHelpers.Left := treeQueryHelpers.Left; + QueryTab.treeHelpers.Constraints.MinWidth := treeQueryHelpers.Constraints.MinWidth; + QueryTab.treeHelpers.PopupMenu := treeQueryHelpers.PopupMenu; + QueryTab.treeHelpers.Images := treeQueryHelpers.Images; + QueryTab.treeHelpers.DragMode := treeQueryHelpers.DragMode; + QueryTab.treeHelpers.DragType := treeQueryHelpers.DragType; + QueryTab.treeHelpers.OnBeforeCellPaint := treeQueryHelpers.OnBeforeCellPaint; + QueryTab.treeHelpers.OnChecking := treeQueryHelpers.OnChecking; + QueryTab.treeHelpers.OnContextPopup := treeQueryHelpers.OnContextPopup; + QueryTab.treeHelpers.OnCreateEditor := treeQueryHelpers.OnCreateEditor; + QueryTab.treeHelpers.OnDblClick := treeQueryHelpers.OnDblClick; + QueryTab.treeHelpers.OnEditing := treeQueryHelpers.OnEditing; + QueryTab.treeHelpers.OnFreeNode := treeQueryHelpers.OnFreeNode; + QueryTab.treeHelpers.OnGetImageIndex := treeQueryHelpers.OnGetImageIndex; + QueryTab.treeHelpers.OnGetText := treeQueryHelpers.OnGetText; + QueryTab.treeHelpers.OnInitChildren := treeQueryHelpers.OnInitChildren; + QueryTab.treeHelpers.OnInitNode := treeQueryHelpers.OnInitNode; + QueryTab.treeHelpers.OnNewText := treeQueryHelpers.OnNewText; + QueryTab.treeHelpers.OnNodeClick := treeQueryHelpers.OnNodeClick; + QueryTab.treeHelpers.OnPaintText := treeQueryHelpers.OnPaintText; + QueryTab.treeHelpers.OnResize := treeQueryHelpers.OnResize; + for i:=0 to treeQueryHelpers.Header.Columns.Count-1 do begin + HelperColumn := QueryTab.treeHelpers.Header.Columns.Add; + HelperColumn.Text := treeQueryHelpers.Header.Columns[i].Text; + HelperColumn.Width := treeQueryHelpers.Header.Columns[i].Width; + end; + QueryTab.treeHelpers.TreeOptions := treeQueryHelpers.TreeOptions; + QueryTab.treeHelpers.Header.Options := treeQueryHelpers.Header.Options; + QueryTab.treeHelpers.Header.AutoSizeIndex := treeQueryHelpers.Header.AutoSizeIndex; + QueryTab.treeHelpers.IncrementalSearch := treeQueryHelpers.IncrementalSearch; + QueryTab.treeHelpers.RootNodeCount := treeQueryHelpers.RootNodeCount; + QueryTab.treeHelpers.TextMargin := treeQueryHelpers.TextMargin; + FixVT(QueryTab.treeHelpers); + + QueryTab.spltQuery := TSplitter.Create(QueryTab.TabSheet); + QueryTab.spltQuery.Parent := QueryTab.TabSheet; + QueryTab.spltQuery.Top := spltQuery.Top; // Important to get it below the editor, not above. See #439 + QueryTab.spltQuery.Align := spltQuery.Align; + QueryTab.spltQuery.Height := spltQuery.Height; + QueryTab.spltQuery.Cursor := spltQuery.Cursor; + QueryTab.spltQuery.ResizeStyle := spltQuery.ResizeStyle; + QueryTab.spltQuery.AutoSnap := spltQuery.AutoSnap; + + QueryTab.ResultTabs := TResultTabs.Create(True); + + QueryTab.tabsetQuery := TTabSet.Create(QueryTab.TabSheet); + QueryTab.tabsetQuery.Name := tabsetQuery.Name + i.ToString; + QueryTab.tabsetQuery.Parent := QueryTab.TabSheet; + // Prevent various problems with alignment of controls. See http://www.heidisql.com/forum.php?t=18924 + QueryTab.tabsetQuery.Top := QueryTab.spltQuery.Top + QueryTab.spltQuery.Height; + QueryTab.tabsetQuery.Align := tabsetQuery.Align; + QueryTab.tabsetQuery.Font.Assign(tabsetQuery.Font); + QueryTab.tabsetQuery.Images := tabsetQuery.Images; + QueryTab.tabsetQuery.Style := tabsetQuery.Style; + QueryTab.tabsetQuery.TabHeight := tabsetQuery.TabHeight; + QueryTab.tabsetQuery.Height := tabsetQuery.Height; + QueryTab.tabsetQuery.TabPosition := tabsetQuery.TabPosition; + QueryTab.tabsetQuery.SoftTop := tabsetQuery.SoftTop; + QueryTab.tabsetQuery.DitherBackground := tabsetQuery.DitherBackground; + QueryTab.tabsetQuery.SelectedColor := tabsetQuery.SelectedColor; + QueryTab.tabsetQuery.UnselectedColor := tabsetQuery.UnselectedColor; + QueryTab.tabsetQuery.OnClick := tabsetQuery.OnClick; + QueryTab.tabsetQuery.OnGetImageIndex := tabsetQuery.OnGetImageIndex; + QueryTab.tabsetQuery.OnMouseMove := tabsetQuery.OnMouseMove; + QueryTab.tabsetQuery.OnMouseLeave := tabsetQuery.OnMouseLeave; + + SetupSynEditor(QueryTab.Memo); + + // Show new tab + if Sender <> actNewQueryTabNofocus then begin + SetMainTab(QueryTab.TabSheet); + QueryTab.Memo.TrySetFocus; + end; +end;} + + +{procedure TMainForm.panelTopDblClick(Sender: TObject); +var + aRect: TRect; + aPoint: TPoint; +begin + // Catch doubleclick on PageControlMain's underlying panel, which gets fired + // when user clicks right besides the visible tabs + aPoint := PageControlMain.ClientOrigin; + aRect := Rect(aPoint.X, aPoint.Y, aPoint.X + PageControlMain.Width, aPoint.Y + PageControlMain.Height - tabQuery.Height); + GetCursorPos(aPoint); + if PtInRect(aRect, aPoint) then + actNewQueryTab.Execute; +end;} + + +{procedure TMainForm.actCloseQueryTabExecute(Sender: TObject); +begin + // Close active query tab by main action + CloseQueryTab(PageControlMain.ActivePageIndex); +end;} + + +{procedure TMainForm.menuCloseQueryTabClick(Sender: TObject); +var + aPoint: TPoint; +begin + // Close query tab by menu item + aPoint := PageControlMain.ScreenToClient(popupMainTabs.PopupPoint); + CloseQueryTab(GetMainTabAt(aPoint.X, aPoint.Y)); +end;} + + +{procedure TMainForm.menuCloseRightQueryTabsClick(Sender: TObject); +var + aPoint: TPoint; + i, PageIndexClick: Integer; +begin + // Close tabs to the right + aPoint := PageControlMain.ScreenToClient(popupMainTabs.PopupPoint); + PageIndexClick := GetMainTabAt(aPoint.X, aPoint.Y); + if PageIndexClick > -1 then begin + for i:=PageControlMain.PageCount-1 downto PageIndexClick+1 do begin + CloseQueryTab(PageControlMain.Pages[i].PageIndex); + end; + end; +end;} + + +{procedure TMainForm.menuCloseTabOnDblClickClick(Sender: TObject); +begin + AppSettings.WriteBool(asTabCloseOnDoubleClick, menuCloseTabOnDblClick.Checked); +end;} + + +{procedure TMainForm.menuCloseTabOnMiddleClickClick(Sender: TObject); +begin + AppSettings.WriteBool(asTabCloseOnMiddleClick, menuCloseTabOnMiddleClick.Checked); +end;} + +{procedure TMainForm.menuTabsInMultipleLinesClick(Sender: TObject); +begin + AppSettings.WriteBool(asTabsInMultipleLines, menuTabsInMultipleLines.Checked); + PageControlMain.MultiLine := menuTabsInMultipleLines.Checked; +end;} + +{procedure TMainForm.actCloseAllQueryTabsExecute(Sender: TObject); +var + i: Integer; +begin + // Close all tabs + for i:=PageControlMain.PageCount-1 downto tabQuery.PageIndex do begin + CloseQueryTab(PageControlMain.Pages[i].PageIndex); + end; +end;} + + +{procedure TMainForm.actRenameQueryTabExecute(Sender: TObject); +var + aPoint: TPoint; + PageIndex: Integer; + NewCaption: String; +begin + // Rename query tab + if Sender = menuRenameQueryTab then begin + aPoint := PageControlMain.ScreenToClient(popupMainTabs.PopupPoint); + PageIndex := GetMainTabAt(aPoint.X, aPoint.Y); + end else begin + PageIndex := PageControlMain.ActivePageIndex; + end; + if not IsQueryTab(PageIndex, True) then begin + // Action may have been triggered through shortcut, and active tab is not a query tab + MessageBeep(MB_ICONASTERISK); + end else begin + NewCaption := PageControlMain.Pages[PageIndex].Caption; + NewCaption := NewCaption.Trim([' ', '*']); + if InputQuery(actRenameQueryTab.Caption, _('Enter new name'), NewCaption) then begin + SetTabCaption(PageIndex, NewCaption); + ValidateQueryControls(Sender); + end; + end; +end;} + + +{procedure TMainForm.actResetPanelDimensionsExecute(Sender: TObject); +var + Tab: TQueryTab; +begin + // Reset probably overlapping panels to their default dimensions + pnlLeft.Width := AppSettings.GetDefaultInt(asDbtreewidth); + SynMemoSQLLog.Height := AppSettings.GetDefaultInt(asLogHeight); + for Tab in QueryTabs do begin + Tab.pnlMemo.Height := AppSettings.GetDefaultInt(asQuerymemoheight); + Tab.pnlHelpers.Width := AppSettings.GetDefaultInt(asQueryhelperswidth); + end; + if pnlPreview.Visible then begin + pnlPreview.Height := AppSettings.GetDefaultInt(asDataPreviewHeight); + end; + AppSettings.DeleteValue(asCompletionProposalWidth); + AppSettings.DeleteValue(asCompletionProposalNbLinesInWindow); + SynCompletionProposal.Width := AppSettings.ReadInt(asCompletionProposalWidth); + SynCompletionProposal.NbLinesInWindow := AppSettings.ReadInt(asCompletionProposalNbLinesInWindow); +end;} + +{procedure TMainForm.menuRenameQueryTabClick(Sender: TObject); +begin + // Rename tab by click on menu item (not by shortcut!) + actRenameQueryTabExecute(Sender); +end;} + + +{procedure TMainForm.popupMainTabsPopup(Sender: TObject); +var + aPoint: TPoint; + PageIndexClick: Integer; +begin + // Detect if there is a tab under mouse position + aPoint := PageControlMain.ScreenToClient(popupMainTabs.PopupPoint); + PageIndexClick := GetMainTabAt(aPoint.X, aPoint.Y); + menuCloseQueryTab.ImageIndex := actCloseQueryTab.ImageIndex; + menuCloseQueryTab.Caption := actCloseQueryTab.Caption; + menuCloseQueryTab.Enabled := IsQueryTab(PageIndexClick, False); + menuCloseRightQueryTabs.Enabled := (QueryTabs.Count > 0) and (PageIndexClick < QueryTabs.Last.TabSheet.PageIndex) and (PageIndexClick > -1); + menuRenameQueryTab.ImageIndex := actRenameQueryTab.ImageIndex; + menuRenameQueryTab.Caption := actRenameQueryTab.Caption; + menuRenameQueryTab.Enabled := IsQueryTab(PageIndexClick, True); + menuCloseTabOnDblClick.Checked := AppSettings.ReadBool(asTabCloseOnDoubleClick); + menuCloseTabOnMiddleClick.Checked := AppSettings.ReadBool(asTabCloseOnMiddleClick); + menuTabsInMultipleLines.Checked := AppSettings.ReadBool(asTabsInMultipleLines) +end;} + + +{procedure TMainForm.CloseQueryTab(PageIndex: Integer); +var + NewPageIndex: Integer; + Grid: TVirtualStringTree; +begin + // Special case: the very first tab gets cleared but not closed + if PageIndex = tabQuery.PageIndex then begin + if ConfirmTabClear(PageIndex, False) then + actClearQueryEditor.Execute; + end; + if not IsQueryTab(PageIndex, False) then + Exit; + // Cancel cell editor if active, preventing crash. See issue #2040 + Grid := ActiveGrid; + if Assigned(Grid) and Grid.IsEditing then + Grid.CancelEditNode; + // Ask user if query content shall be saved to disk + if not ConfirmTabClose(PageIndex, False) then + Exit; + // Block too quick further close actions, fix issue #1496. Action gets enabled again in PageControlMainChange/ValidateQueryControls + actCloseQueryTab.Enabled := False; + // Work around bugs in ComCtrls.TPageControl.RemovePage + NewPageIndex := PageControlMain.ActivePageIndex; + if NewPageIndex >= PageIndex then + Dec(NewPageIndex); + // Avoid excessive flicker: + LockWindowUpdate(PageControlMain.Handle); + PageControlMain.Pages[PageIndex].Free; + QueryTabs.Delete(PageIndex-tabQuery.PageIndex); + PageControlMain.ActivePageIndex := NewPageIndex; + FixQueryTabCloseButtons; + LockWindowUpdate(0); + PageControlMain.OnChange(PageControlMain); +end;} + + +{procedure TMainForm.actFavoriteObjectsOnlyExecute(Sender: TObject); +begin + // Click on "tree favorites" main button + editDatabaseTableFilterChange(Sender); + if actFavoriteObjectsOnly.Checked then + actFavoriteObjectsOnly.ImageIndex := 112 + else + actFavoriteObjectsOnly.ImageIndex := 113; +end;} + + +{procedure TMainForm.buttonedEditClear(Sender: TObject); +begin + // Click on "clear" button of any TButtonedEdit control + TButtonedEdit(Sender).Clear; +end;} + + +{procedure TMainForm.editDatabaseTableFilterChange(Sender: TObject); +var + Node: PVirtualNode; + Obj: PDBObject; + rxdb, rxtable: TRegExpr; + NodeMatches: Boolean; + Errors: TStringList; +begin + // Immediately apply database filter + LogSQL('editDatabaseTableFilterChange', lcDebug); + + rxdb := TRegExpr.Create; + rxdb.ModifierI := True; + rxdb.Expression := '('+StringReplace(editDatabaseFilter.Text, ';', '|', [rfReplaceAll])+')'; + rxtable := TRegExpr.Create; + rxtable.ModifierI := True; + rxtable.Expression := '('+StringReplace(editTableFilter.Text, ';', '|', [rfReplaceAll])+')'; + + Errors := TStringList.Create; + + DBtree.BeginUpdate; + Node := DBtree.GetFirst; + while Assigned(Node) do begin + Obj := DBtree.GetNodeData(Node); + NodeMatches := True; + try + case Obj.NodeType of + lntDb: begin + // Match against database filter + if editDatabaseFilter.Text <> '' then + NodeMatches := rxdb.Exec(DBtree.Text[Node, 0]); + end; + lntTable..lntEvent: begin + // Match against table filter + if editTableFilter.Text <> '' then + NodeMatches := rxtable.Exec(DBtree.Text[Node, 0]); + if actFavoriteObjectsOnly.Checked then + // Hide non-favorite object path + NodeMatches := NodeMatches and (Obj.Connection.Favorites.IndexOf(Obj.Path) > -1); + end; + end; + except + on E:Exception do begin + // Log regex errors, but avoid duplicate messages + if Errors.IndexOf(E.Message) = -1 then begin + LogSQL(E.Message); + Errors.Add(E.Message); + end; + end; + end; + DBtree.IsVisible[Node] := NodeMatches; + + Node := DBtree.GetNextInitialized(Node); + end; + DBtree.EndUpdate; + // Fix scroll height of the tree. See issue #2063 and #2002 + DBtree.Repaint; + + rxdb.Free; + rxtable.Free; + + editDatabaseFilter.RightButton.Visible := editDatabaseFilter.Text <> ''; + editTableFilter.RightButton.Visible := editTableFilter.Text <> ''; +end;} + + +{procedure TMainForm.editDatabaseTableFilterLeftButtonClick(Sender: TObject); +var + Menu: TPopupMenu; + Item: TMenuItem; + P: TPoint; + Edit: TButtonedEdit; + History: TStringList; + ItemText: String; + Setting: TAppSettingIndex; +begin + // Create right menu with filter history + Edit := Sender as TButtonedEdit; + Menu := TPopupMenu.Create(Edit); + Menu.AutoHotkeys := maManual; + Menu.Images := VirtualImageListMain; + AppSettings.SessionPath := ''; + if Edit = editDatabaseFilter then + Setting := asDatabaseFilter + else if Edit = editTableFilter then + Setting := asTableFilter + else if Edit = editFilterVT then + Setting := asFilterVT + else + raise Exception.CreateFmt(MsgUnhandledControl, ['editDatabaseTableFilterLeftButtonClick']); + History := TStringList.Create; + History.Text := AppSettings.ReadString(Setting); + for ItemText in History do begin + Item := TMenuItem.Create(Menu); + Item.Caption := ItemText; + Item.OnClick := editDatabaseTableFilterMenuClick; + Item.Tag := 0; + Item.Checked := ItemText = Edit.Text; + Menu.Items.Add(Item); + end; + History.Free; + + Item := TMenuItem.Create(Menu); + Item.Caption := _('Clear'); + Item.ImageIndex := 193; + Item.OnClick := editDatabaseTableFilterMenuClick; + Item.Tag := 1; + Item.Enabled := Edit.Text <> ''; + Menu.Items.Add(Item); + + Item := TMenuItem.Create(Menu); + Item.Caption := _('Empty recent filters'); + Item.ImageIndex := 26; + Item.OnClick := editDatabaseTableFilterMenuClick; + Item.Tag := 2; + Item.Enabled := Menu.Items.Count > 1; + Menu.Items.Add(Item); + + P := Edit.ClientToScreen(Edit.ClientRect.TopLeft); + Menu.Popup(p.X, p.Y+16); +end;} + + +{procedure TMainForm.editDatabaseTableFilterMenuClick(Sender: TObject); +var + Menu: TPopupMenu; + Item: TMenuItem; + Edit: TButtonedEdit; + Setting: TAppSettingIndex; +begin + // Insert text from filter history menu + Item := Sender as TMenuItem; + Menu := Item.Owner as TPopupMenu; + Edit := Menu.Owner as TButtonedEdit; + if Edit = editDatabaseFilter then + Setting := asDatabaseFilter + else if Edit = editTableFilter then + Setting := asTableFilter + else if Edit = editFilterVT then + Setting := asFilterVT + else + raise Exception.CreateFmt(MsgUnhandledControl, ['editDatabaseTableFilterMenuClick']); + case Item.Tag of + 0: Edit.Text := Item.Caption; + 1: Edit.Clear; + 2: AppSettings.DeleteValue(Setting); + end; + Menu.Free; +end;} + + +{procedure TMainForm.editDatabaseTableFilterExit(Sender: TObject); +var + History: TStringList; + Edit: TButtonedEdit; + i: Integer; + Setting: TAppSettingIndex; +begin + // Add (move) custom filter text to (in) drop down history, if not empty + Edit := Sender as TButtonedEdit; + AppSettings.SessionPath := ''; + if Edit = editDatabaseFilter then + Setting := asDatabaseFilter + else if Edit = editTableFilter then + Setting := asTableFilter + else if Edit = editFilterVT then + Setting := asFilterVT + else + raise Exception.CreateFmt(MsgUnhandledControl, ['editDatabaseTableFilterExit']); + History := TStringList.Create; + History.Text := AppSettings.ReadString(Setting); + i := History.IndexOf(Edit.Text); + if i > -1 then + History.Delete(i); + History.Insert(0, Edit.Text); + for i:=History.Count-1 downto 0 do begin + if (i >= 20) or (Trim(History[i]) = '') then + History.Delete(i); + end; + AppSettings.WriteString(Setting, History.Text); + History.Free; +end;} + + +{procedure TMainForm.CloseButtonOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +begin + FLastMouseDownCloseButton := Sender; +end;} + + +{procedure TMainForm.CloseButtonOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +begin + // Click on "Close" button of Query tab + if Button <> mbLeft then + Exit; + // Between MousDown and MouseUp it is possible that the focused tab has switched. As we simulate a mouse-click + // here, we must check if also the MouseDown event was fired on this particular button. See issue #1469. + if (Sender <> FLastMouseDownCloseButton) then + Exit; + // Prevent EAccessViolation in TControl.GetClientWidth, see issue #1640 + TimerCloseTabByButton.Enabled := True; +end;} + + +{procedure TMainForm.TimerCloseTabByButtonTimer(Sender: TObject); +var + i: Integer; +begin + // Asynchronous timer for mousedown event on query tab close button + TimerCloseTabByButton.Enabled := False; + for i:=0 to QueryTabs.Count-1 do begin + if QueryTabs[i].CloseButton = FLastMouseDownCloseButton then begin + CloseQueryTab(QueryTabs[i].TabSheet.PageIndex); + break; + end; + end; +end;} + + +{procedure TMainForm.PageControlMainMouseUp(Sender: TObject; + Button: TMouseButton; Shift: TShiftState; X, Y: Integer); +var + CurTickcount: Cardinal; + TabNumber: Integer; +begin + // Handle click event on poor PageControl tabs in lack of an OnClick + case Button of + mbLeft: begin + // Simulate doubleclick on tab to close it + if AppSettings.ReadBool(asTabCloseOnDoubleClick) then begin + CurTickcount := GetTickCount; + TabNumber := GetMainTabAt(X, Y); + if (TabNumber = FLastTabNumberOnMouseUp) + and (CurTickcount - FLastMouseUpOnPageControl <= GetDoubleClickTime) then begin + CloseQueryTab(TabNumber); + end else begin + FLastMouseUpOnPageControl := CurTickcount; + FLastTabNumberOnMouseUp := TabNumber; + end; + end; + end; + + mbMiddle: begin + // Middle click on tab + if AppSettings.ReadBool(asTabCloseOnMiddleClick) then begin + TabNumber := GetMainTabAt(X, Y); + CloseQueryTab(TabNumber); + end; + end; + end; + +end;} + + +{function TMainForm.GetMainTabAt(X, Y: Integer): Integer; +var + i: Integer; +begin + // Return page index of main tab by coordinates + Result := PageControlMain.IndexOfTabAt(X, Y); + for i:=0 to PageControlMain.PageCount-1 do begin + if (i<=Result) and (not PageControlMain.Pages[i].TabVisible) then + Inc(Result); + end; +end;} + + +{procedure TMainForm.FixQueryTabCloseButtons; +var + i, PageIndex, VisiblePageIndex: Integer; + Rect: TRect; + btn: TSpeedButton; +begin + // Fix positions of "Close" buttons on Query tabs + // Avoid AV on Startup, when Mainform.OnResize is called once or twice implicitely. + if not Assigned(FBtnAddTab) then + Exit; + for PageIndex:=tabQuery.PageIndex+1 to PageControlMain.PageCount-1 do begin + VisiblePageIndex := PageIndex; + for i:=0 to PageControlMain.PageCount-1 do begin + if (i<=VisiblePageIndex) and (not PageControlMain.Pages[i].TabVisible) then + Dec(VisiblePageIndex); + end; + Rect := PageControlMain.TabRect(VisiblePageIndex); + btn := QueryTabs[PageIndex-tabQuery.PageIndex].CloseButton; + btn.Top := Rect.Top + 2; + btn.Left := Rect.Right - 19; + end; + // Set position of "Add tab" button + VisiblePageIndex := PageControlMain.PageCount-1; + for i:=0 to PageControlMain.PageCount-1 do begin + if not PageControlMain.Pages[i].TabVisible then + Dec(VisiblePageIndex); + end; + Rect := PageControlMain.TabRect(VisiblePageIndex); + FBtnAddTab.Top := Rect.Top; + FBtnAddTab.Left := Rect.Right + 5; +end;} + + +{function TMainForm.GetOrCreateEmptyQueryTab(DoFocus: Boolean): TQueryTab; +var + i: Integer; +begin + // Return either a) current query tab if one is active + // or b) the first empty one + // or c) create a new one + // Result should never be nil, unlike in QueryTabs.ActiveTab + Result := nil; + // Search empty tab + for i:=0 to QueryTabs.Count-1 do begin + if (QueryTabs[i].MemoFilename='') and (QueryTabs[i].Memo.GetTextLen=0) then begin + Result := QueryTabs[i]; + if DoFocus then + SetMainTab(Result.TabSheet); + Break; + end; + end; + // Create new tab + if Result = nil then begin + if DoFocus then + actNewQueryTabExecute(actNewQueryTab) + else + actNewQueryTabExecute(actNewQueryTabNofocus); + Result := QueryTabs[QueryTabs.Count-1]; + end; +end;} + + +{function TMainForm.ActiveSynMemo(AcceptReadOnlyMemo: Boolean): TSynMemo; +var + Control: TWinControl; +begin + Result := nil; + Control := Screen.ActiveControl; + if Control is TCustomSynEdit then begin + Result := Control as TSynMemo; + // We have a few readonly-SynMemos which we'll ignore here + if (not AcceptReadOnlyMemo) and Result.ReadOnly then + Result := nil; + end; + if (not Assigned(Result)) and QueryTabs.HasActiveTab then + Result := QueryTabs.ActiveMemo; + if (not Assigned(Result)) and (Screen.ActiveForm is TfrmTextEditor) then begin + Result := TfrmTextEditor(Screen.ActiveForm).MemoText; + end; + +end;} + + +{function TMainForm.ActiveGrid: TVirtualStringTree; +begin + // Return current data or query grid, if main form is active + Result := nil; + if Screen.ActiveForm <> Self then + Exit; + if PageControlMain.ActivePage = tabData then + Result := DataGrid + else if (QueryTabs.ActiveTab <> nil) and (QueryTabs.ActiveTab.ActiveResultTab <> nil) then + Result := QueryTabs.ActiveTab.ActiveResultTab.Grid; +end;} + + +{function TMainForm.GridResult(Grid: TBaseVirtualTree): TDBQuery; +var + QueryTab: TQueryTab; + CurrentTab: TTabSheet; + ResultTab: TResultTab; +begin + // All grids (data- and query-grids, also host subtabs) are placed directly on a TTabSheet + Result := nil; + if Grid = DataGrid then begin + if DataGridResult<>nil then + Result := DataGridResult; + end else if Assigned(Grid) then begin + CurrentTab := Grid.Parent as TTabSheet; + if CurrentTab.Parent = PageControlHost then + Result := FHostListResults[CurrentTab.PageIndex] + else for QueryTab in QueryTabs do begin + if QueryTab.TabSheet = CurrentTab then begin + for ResultTab in QueryTab.ResultTabs do begin + if ResultTab.Grid = Grid then begin + Result := ResultTab.Results; + break; + end; + end; + end; + end; + end; +end;} + + +{function TMainForm.IsQueryTab(PageIndex: Integer; IncludeFixed: Boolean): Boolean; +var + Min: Integer; +begin + // Find out if the given main tab is a query tab + Min := tabQuery.PageIndex+1; + if IncludeFixed then Dec(Min); + Result := PageIndex >= Min; +end;} + +{procedure TMainForm.SetWindowCaption; +var + Cap: String; +begin + // Set window caption and taskbar text + Cap := DBtree.Path(DBtree.FocusedNode, 0, '\') + ' - ' + APPNAME; + if AppSettings.PortableMode then + Cap := Cap + ' Portable'; + Cap := Cap + ' ' + FAppVersion; + Caption := Cap; + Application.Title := Cap; +end;} + + +{procedure TMainForm.SetMainTab(Page: TTabSheet); +begin + // Safely switch main tab + if (Page <> nil) and (not FTreeRefreshInProgress) then begin + PagecontrolMain.ActivePage := Page; + PageControlMain.OnChange(Page); + end; +end;} + + +{procedure TMainForm.SetTabCaption(PageIndex: Integer; Text: String); +var + Tab: TQueryTab; +begin + // The current tab can be closed already if we're here after CloseQueryTab() + if PageIndex >= PageControlMain.PageCount then + Exit; + // Some cases pass -1 which triggers a "List index out of bounds" in below cast + if PageIndex = -1 then + Exit; + // Escape hotkey accelerator in name of session, database or table + Text := EscapeHotkeyPrefix(Text); + Text := StrEllipsis(Text, 70); + // Special case if passed text is empty: Reset query tab caption to "Query #123" + if (PageIndex = tabQuery.PageIndex) and (Text = '') then + Text := _('Query'); + if IsQueryTab(PageIndex, False) then begin + if Text = '' then begin + for Tab in QueryTabs do begin + if Tab.TabSheet = PageControlMain.Pages[PageIndex] then begin + Text := _('Query')+' #'+IntToStr(Tab.Number); + break; + end; + end; + end; + // Leave space for close button on closable query tabs + Text := Text + ' '; + end; + PageControlMain.Pages[PageIndex].Caption := Text; + FixQueryTabCloseButtons; +end;} + + +{function TMainForm.ConfirmTabClose(PageIndex: Integer; AppIsClosing: Boolean): Boolean; +var + Tab: TQueryTab; +begin + Tab := QueryTabs[PageIndex-tabQuery.PageIndex]; + if Tab.QueryRunning then begin + LogSQL(_('Cannot close tab with running query. Please wait until query has finished.')); + Result := False; + end else if not Tab.Memo.Modified then begin + Result := True; + end else begin + Result := ConfirmTabClear(PageIndex, AppIsClosing); + end; +end;} + + +{function TMainForm.ConfirmTabClear(PageIndex: Integer; AppIsClosing: Boolean): Boolean; +var + msg: String; + Tab: TQueryTab; + Dialog: TExtFileSaveDialog; + MsgButtons: TMsgDlgButtons; +begin + Tab := QueryTabs[PageIndex-tabQuery.PageIndex]; + + // Unhide tabsheet so the user sees the memo content. + // If the dialog is suppressed anyway, the user does not need to see the text, and we avoid + // storing this as the focused tab + if AppSettings.ReadBool(asPromptSaveFileOnTabClose) then begin + Tab.TabSheet.PageControl.ActivePage := Tab.TabSheet; + end; + + // Prompt for saving unsaved contents + if Tab.MemoFilename <> '' then + msg := f_('Save changes to file %s ?', [Tab.MemoFilename]) + else + msg := f_('Save content of tab "%s"?', [Trim(Tab.TabSheet.Caption)]); + if AppSettings.RestoreTabsInitValue and AppIsClosing then begin + msg := msg + CRLF + CRLF + _('Your code is saved anyway, as auto-restoring is activated.'); + end; + + if FConnections.Count > 0 then + MsgButtons := [mbYes, mbNo, mbCancel] + else + MsgButtons := [mbYes, mbNo]; + + case MessageDialog(_('Modified query'), msg, mtConfirmation, MsgButtons, asPromptSaveFileOnTabClose) of + mrNo: Result := True; + mrYes: begin + if Tab.MemoFilename <> '' then begin + Tab.SaveContents(Tab.MemoFilename, False); + Result := True; + end + else begin + Dialog := TExtFileSaveDialog.Create(Self); + Dialog.Options := Dialog.Options + [fdoOverwritePrompt]; + Dialog.AddFileType('*.sql', _('SQL files')); + Dialog.AddFileType('*.*', _('All files')); + Dialog.DefaultExtension := 'sql'; + Dialog.LineBreakIndex := Tab.MemoLineBreaks; + if Dialog.Execute then begin + Tab.SaveContents(Dialog.FileName, False); + Tab.MemoLineBreaks := Dialog.LineBreakIndex; + end; + // The save dialog can be cancelled. + Result := not Tab.Memo.Modified; + Dialog.Free; + end; + end; + else Result := False; + end; + + // Auto-backup logic + if AppSettings.RestoreTabsInitValue then begin + if AppIsClosing then begin + // Do last backup before app closes + Tab.BackupUnsavedContent; + end else begin + // Delete backup file if tab is closed by user, intentionally + if (not Tab.MemoBackupFilename.IsEmpty) and FileExists(Tab.MemoBackupFilename) then begin + if not DeleteFileWithUndo(Tab.MemoBackupFilename) then begin + ErrorDialog(f_('Backup file could not be deleted: %s', [Tab.MemoBackupFilename])); + end; + end; + end; + end; + + if (not Result) and (FConnections.Count = 0) then begin + // Upper right "X" button clicked, or save dialog cancelled + Result := True; + end; + + ValidateControls(Self); +end;} + + +{procedure TMainForm.actFilterPanelExecute(Sender: TObject); +begin + // (De-)activate or focus filter panel + if Sender <> actFilterPanel then + actFilterPanel.Checked := not actFilterPanel.Checked; + pnlFilterVT.Visible := actFilterPanel.Checked; + // On startup, we cannot SetFocus, throws exceptons. Call with nil in that special case - see FormCreate + if pnlFilterVT.Visible and editFilterVT.CanFocus and (Sender <> nil) then + editFilterVT.SetFocus; + UpdateFilterPanel(Sender); +end;} + + +{procedure TMainForm.UpdateFilterPanel(Sender: TObject); +var + tab: TTabSheet; + f: String; +begin + // Called when active tab changes + pnlFilterVT.Enabled := (PageControlMain.ActivePage <> tabEditor) or (ActiveObjectEditor is TfrmTableEditor); + lblFilterVT.Enabled := pnlFilterVT.Enabled; + editFilterVT.Enabled := pnlFilterVT.Enabled; + lblFilterVTInfo.Enabled := pnlFilterVT.Enabled; + if pnlFilterVT.Enabled then + editFilterVT.Color := GetThemeColor(clWindow) + else + editFilterVT.Color := GetThemeColor(clBtnFace); + + tab := PageControlMain.ActivePage; + if tab = tabHost then + tab := PageControlHost.ActivePage; + if not pnlFilterVT.Visible then begin + if editFilterVT.Text <> '' then + editFilterVT.Text := '' + else + editFilterVTChange(Sender); + end else begin + if tab = tabDatabases then f := FFilterTextDatabases + else if tab = tabVariables then f := FFilterTextVariables + else if tab = tabStatus then f := FFilterTextStatus + else if tab = tabProcesslist then f := FFilterTextProcessList + else if tab = tabCommandStats then f := FFilterTextCommandStats + else if tab = tabDatabase then f := FFilterTextDatabase + else if tab = tabEditor then f := FFilterTextEditor + else if tab = tabData then f := FFilterTextData + else if QueryTabs.HasActiveTab and (QueryTabs.ActiveTab.ActiveResultTab <> nil) then f := QueryTabs.ActiveTab.ActiveResultTab.FilterText; + if editFilterVT.Text <> f then + editFilterVT.Text := f + else + editFilterVTChange(Sender); + end; +end;} + + +{procedure TMainform.SetupSynEditors; +var + i, j: Integer; + Editors: TObjectList; + BaseEditor: TSynMemo; + KeyStroke: TSynEditKeyStroke; + Attri: TSynHighlighterAttributes; + Shortcut1, Shortcut2: TShortcut; +begin + // Setup all known TSynMemo's + // This version includes global settings for keyboard shortcut, highlighting and completion proposal + Editors := TObjectList.Create(False); + BaseEditor := SynMemoQuery; + for i:=0 to QueryTabs.Count-1 do + Editors.Add(QueryTabs[i].Memo); + Editors.Add(SynMemoFilter); + Editors.Add(SynMemoProcessView); + Editors.Add(SynMemoSQLLog); + if Assigned(ActiveObjectEditor) then + FindComponentInstances(ActiveObjectEditor, TSynMemo, Editors); + if Assigned(frmPreferences) then + Editors.Add(frmPreferences.SynMemoSQLSample); + if Assigned(FCreateDatabaseDialog) then + Editors.Add(FCreateDatabaseDialog.SynMemoCreateCode); + if SqlHelpDialog <> nil then begin + Editors.Add(SqlHelpDialog.memoDescription); + Editors.Add(SqlHelpDialog.MemoExample); + end; + if Assigned(FTableToolsDialog) then + Editors.Add(FTableToolsDialog.SynMemoFindText); + if Assigned(frmCsvDetector) then + Editors.Add(frmCsvDetector.SynMemoCreateTable); + + if AppSettings.ReadBool(asTabsToSpaces) then + BaseEditor.Options := BaseEditor.Options + [eoTabsToSpaces] + else + BaseEditor.Options := BaseEditor.Options - [eoTabsToSpaces]; + FMatchingBraceForegroundColor := StringToColor(AppSettings.ReadString(asSQLColMatchingBraceForeground)); + FMatchingBraceBackgroundColor := StringToColor(AppSettings.ReadString(asSQLColMatchingBraceBackground)); + FSynEditInOnPaintTransient := False; + + // Shortcuts + for j:=0 to BaseEditor.Keystrokes.Count-1 do begin + KeyStroke := BaseEditor.Keystrokes[j]; + Shortcut1 := AppSettings.ReadInt(asActionShortcut1, EditorCommandToCodeString(Keystroke.Command)); + Shortcut2 := AppSettings.ReadInt(asActionShortcut2, EditorCommandToCodeString(Keystroke.Command)); + try + if Shortcut1<>0 then + Keystroke.ShortCut := Shortcut1; + if Shortcut2<>0 then + Keystroke.ShortCut2 := Shortcut2; + except + on E:ESynKeyError do begin + LogSQL(f_('Could not apply SynEdit keystroke shortcut "%s" (or secondary: "%s") to %s. %s. Please go to %s > %s > %s to change this settings.', + [ + ShortCutToText(Shortcut1), + ShortCutToText(Shortcut2), + EditorCommandToCodeString(Keystroke.Command), + E.Message, + _('Tools'), + _('Preferences'), + _('Shortcuts') + ]), + lcError); + end; + end; + end; + // Apply events and options to all known editors + for i:=0 to Editors.Count-1 do begin + SetupSynEditor(Editors[i] as TSynMemo); + end; + Editors.Free; + // Highlighting + for i:=0 to SynSQLSynUsed.AttrCount - 1 do begin + Attri := SynSQLSynUsed.Attribute[i]; + Attri.Foreground := AppSettings.ReadInt(asHighlighterForeground, Attri.Name, Attri.Foreground); + Attri.Background := AppSettings.ReadInt(asHighlighterBackground, Attri.Name, Attri.Background); + // IntegerStyle gathers all font styles (bold, italic, ...) in one number + Attri.IntegerStyle := AppSettings.ReadInt(asHighlighterStyle, Attri.Name, Attri.IntegerStyle); + end; + // Completion proposal + if AppSettings.ReadBool(asCompletionProposalSearchOnMid) then + SynCompletionProposal.Options := SynCompletionProposal.Options + [scoLimitToMatchedTextAnywhere] + else + SynCompletionProposal.Options := SynCompletionProposal.Options - [scoLimitToMatchedTextAnywhere]; +end;} + + +{procedure TMainForm.SetupSynEditors(BaseForm: TComponent); +var + Editors: TObjectList; + i: Integer; +begin + // Restore font, highlighter and shortcuts for all TSynMemo's in given base form + Editors := TObjectList.Create(False); + FindComponentInstances(BaseForm, TSynMemo, Editors); + for i:=0 to Editors.Count-1 do begin + SetupSynEditor(Editors[i] as TSynMemo); + end; + Editors.Free; +end;} + + +{procedure TMainForm.SetupSynEditor(Editor: TSynMemo); +var + BaseEditor: TSynMemo; +begin + LogSQL('Setting up TSynMemo "'+Editor.Name+'"', lcDebug); + BaseEditor := SynMemoQuery; + Editor.Color := GetThemeColor(clWindow); + Editor.ScrollHintColor := GetThemeColor(clInfoBk); + Editor.Font.Name := AppSettings.ReadString(asFontName); + Editor.Font.Size := AppSettings.ReadInt(asFontSize); + Editor.Gutter.BorderColor := GetThemeColor(clWindow); + Editor.Gutter.Font.Name := Editor.Font.Name; + Editor.Gutter.Font.Size := Editor.Font.Size; + Editor.Gutter.Font.Color := BaseEditor.Gutter.Font.Color; + Editor.Gutter.AutoSize := BaseEditor.Gutter.AutoSize; + Editor.Gutter.DigitCount := BaseEditor.Gutter.DigitCount; + Editor.Gutter.LeftOffset := BaseEditor.Gutter.LeftOffset; + Editor.Gutter.RightOffset := BaseEditor.Gutter.RightOffset; + Editor.Gutter.ShowLineNumbers := BaseEditor.Gutter.ShowLineNumbers; + if Editor <> SynMemoSQLLog then begin + Editor.WordWrap := actQueryWordWrap.Checked; + // Assignment of OnScanForFoldRanges event is required for UseCodeFolding + Editor.OnScanForFoldRanges := BaseEditor.OnScanForFoldRanges; + Editor.UseCodeFolding := actCodeFolding.Checked; + end; + Editor.ActiveLineColor := StringToColor(AppSettings.ReadString(asSQLColActiveLine)); + Editor.Options := BaseEditor.Options; + if Editor = SynMemoSQLLog then + Editor.Options := Editor.Options + [eoRightMouseMovesCursor]; + Editor.TabWidth := AppSettings.ReadInt(asTabWidth); + Editor.MaxScrollWidth := BaseEditor.MaxScrollWidth; + Editor.WantTabs := BaseEditor.WantTabs; + Editor.HintMode := BaseEditor.HintMode; + Editor.OnKeyPress := BaseEditor.OnKeyPress; + Editor.OnMouseWheel := BaseEditor.OnMouseWheel; + Editor.OnTokenHint := BaseEditor.OnTokenHint; + if Editor <> SynMemoSQLLog then begin + Editor.OnPaintTransient := BaseEditor.OnPaintTransient; + end; + // Don't reapply shortcuts to base editor again, see issue 1600 + if Editor <> BaseEditor then begin + Editor.Keystrokes := BaseEditor.KeyStrokes; + end; +end;} + + +{procedure TMainForm.actReformatSQLExecute(Sender: TObject); +var + m: TCustomSynEdit; + CursorPosStart, CursorPosEnd: Integer; + Done: Boolean; +begin + // Reformat SQL query + m := ActiveSynMemo(False); + if not Assigned(m) then begin + ErrorDialog(_('Cannot reformat'), _('Please select a non-readonly SQL editor first.')); + Exit; + end; + CursorPosStart := m.SelStart; + CursorPosEnd := m.SelEnd; + if not m.SelAvail then + m.SelectAll; + if m.SelLength = 0 then + ErrorDialog(_('Cannot reformat'), _('The current editor is empty.')) + else begin + frmReformatter := TfrmReformatter.Create(Self); + frmReformatter.InputCode := m.SelText; + if AppSettings.ReadInt(asReformatterNoDialog) <> 0 then begin + frmReformatter.btnOkClick(Self); + Done := True; + end + else begin + Done := frmReformatter.ShowModal = mrOk; + end; + if Done then begin + Screen.Cursor := crHourglass; + m.UndoList.AddGroupBreak; + m.SelText := frmReformatter.OutputCode; + m.SelStart := CursorPosStart; + if CursorPosEnd > CursorPosStart then + m.SelEnd := CursorPosStart + Length(frmReformatter.OutputCode); + m.UndoList.AddGroupBreak; + Screen.Cursor := crDefault; + end; + frmReformatter.Free; + end; +end;} + + +{procedure TMainForm.PageControlMainContextPopup(Sender: TObject; MousePos: TPoint; var Handled: Boolean); +var + ClickPoint: TPoint; + TabsHeight: Integer; +begin + // Activate tab popup menu only when clicked on tabs area. + TabsHeight := (FBtnAddTab.Height+2) * PageControlMain.RowCount; + if MousePos.Y <= TabsHeight then begin + ClickPoint := PageControlMain.ClientToScreen(MousePos); + popupMainTabs.Popup(ClickPoint.X, ClickPoint.Y); + Handled := True; + end else + Handled := False; +end;} + + +{procedure TMainForm.menuQueryHelpersGenerateStatementClick(Sender: TObject); +var + MenuItem: TMenuItem; + sql, Val, WhereClause: String; + i, idx: Integer; + ColumnNames, DefaultValues: TStringList; + KeyColumns: TTableColumnList; + Column: TTableColumn; + Tree: TVirtualStringTree; + Node: PVirtualNode; +begin + // Generate SELECT, INSERT, UPDATE or DELETE query using selected columns + MenuItem := (Sender as TMenuItem); + ColumnNames := TStringList.Create; + DefaultValues := TStringList.Create; + Tree := QueryTabs.ActiveHelpersTree; + Node := Tree.GetFirstChild(FindNode(Tree, TQueryTab.HelperNodeColumns, nil)); + while Assigned(Node) do begin + if Tree.Selected[Node] then begin + Column := SelectedTableColumns[Node.Index]; + ColumnNames.Add(Column.Connection.QuoteIdent(Tree.Text[Node, 0], False)); + case Column.DataType.Category of + dtcInteger, dtcReal: Val := '0'; + dtcText, dtcOther: begin + Val := Column.Connection.EscapeString(Column.DefaultText); + if Column.DefaultType in [cdtNull] then + Val := Column.Connection.EscapeString('') + else + Val := Column.Connection.EscapeString(Column.DefaultText); + end; + dtcTemporal: Val := 'NOW()'; + else Val := 'NULL'; + end; + if Column.DefaultType = cdtAutoInc then + Val := 'NULL'; + DefaultValues.Add(Val); + end; + Node := Tree.GetNextSibling(Node); + end; + KeyColumns := ActiveConnection.GetKeyColumns(SelectedTableColumns, SelectedTableKeys); + WhereClause := ''; + for i:=0 to KeyColumns.Count-1 do begin + idx := ColumnNames.IndexOf(ActiveConnection.QuoteIdent(KeyColumns[i].Name, False)); + if idx > -1 then begin + if WhereClause <> '' then + WhereClause := WhereClause + ' AND '; + WhereClause := WhereClause + ActiveConnection.QuoteIdent(KeyColumns[i].Name, False)+'='+DefaultValues[idx]; + end; + end; + + + if MenuItem = menuQueryHelpersGenerateSelect then begin + sql := 'SELECT ' + Implode(', ', ColumnNames) + SLineBreak + + CodeIndent + 'FROM '+ActiveDbObj.QuotedName(False); + + end else if MenuItem = menuQueryHelpersGenerateInsert then begin + sql := 'INSERT INTO ' + ActiveDbObj.QuotedName(False) + SLineBreak + + CodeIndent + '(' + Implode(', ', ColumnNames) + ')' + SLineBreak + + CodeIndent + 'VALUES (' + Implode(', ', DefaultValues) + ')'; + + end else if MenuItem = menuQueryHelpersGenerateUpdate then begin + sql := 'UPDATE ' + ActiveDbObj.QuotedName(False) + SLineBreak + CodeIndent + 'SET' + SLineBreak; + if ColumnNames.Count > 0 then begin + for i:=0 to ColumnNames.Count-1 do begin + sql := sql + CodeIndent(2) + ColumnNames[i] + '=' + DefaultValues[i] + ',' + SLineBreak; + end; + Delete(sql, Length(sql)-2, 1); + end else + sql := sql + CodeIndent(2) + '??? # No column names selected!' + SLineBreak; + sql := sql + CodeIndent + 'WHERE ' + WhereClause; + + end else if MenuItem = menuQueryHelpersGenerateDelete then begin + sql := 'DELETE FROM '+ActiveDbObj.QuotedName(False)+' WHERE ' + WhereClause; + + end; + QueryTabs.ActiveMemo.UndoList.AddGroupBreak; + QueryTabs.ActiveMemo.SelText := sql; +end;} + + +{procedure TMainForm.DBtreeAfterCellPaint(Sender: TBaseVirtualTree; + TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; + CellRect: TRect); +var + Obj: PDBObject; +begin + // Paint favorite icon on table node + if Column <> 0 then + Exit; + Obj := Sender.GetNodeData(Node); + if Obj.NodeType in [lntTable..lntEvent] then begin + if Obj.Connection.Favorites.IndexOf(Obj.Path) > -1 then + VirtualImageListMain.Draw(TargetCanvas, CellRect.Left, CellRect.Top, 168) + else if Node = Sender.HotNode then + VirtualImageListMain.Draw(TargetCanvas, CellRect.Left, CellRect.Top, 183); + end; +end;} + + +{procedure TMainForm.DBtreeMouseUp(Sender: TObject; Button: TMouseButton; + Shift: TShiftState; X, Y: Integer); +var + Obj: PDBObject; + Node: PVirtualNode; + idx: Integer; +begin + // Watch out for clicks on favorite icon + // Add or remove object path from favorite list on click + Node := DBtree.GetNodeAt(X, Y); + if (Button = mbLeft) and (X < VirtualImageListMain.Width) and Assigned(Node) then begin + Obj := DBtree.GetNodeData(Node); + if Obj.NodeType in [lntTable..lntEvent] then begin + idx := Obj.Connection.Favorites.IndexOf(Obj.Path); + if idx > -1 then + Obj.Connection.Favorites.Delete(idx) + else + Obj.Connection.Favorites.Add(Obj.Path); + DBtree.RepaintNode(Node); + AppSettings.SessionPath := Obj.Connection.Parameters.SessionPath; + AppSettings.WriteString(asFavoriteObjects, Obj.Connection.Favorites.Text); + end; + end; +end;} + + +{procedure TMainForm.DBtreeBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + var ContentRect: TRect); +var + DBObj: PDBObject; + AllObjects: TDBObjectList; +begin + if CellPaintMode=cpmPaint then try + DBObj := Sender.GetNodeData(Node); + if DbObj.Connection.Parameters.SessionColor <> AppSettings.GetDefaultInt(asTreeBackground) then begin + TargetCanvas.Brush.Color := DbObj.Connection.Parameters.SessionColor; + TargetCanvas.FillRect(CellRect); + end; + if (Column=1) and DBObj.Connection.DbObjectsCached(DBObj.Database) then begin + AllObjects := DBObj.Connection.GetDBObjects(DBObj.Database); + PaintColorBar(DBObj.Size, AllObjects.LargestObjectSize, TargetCanvas, CellRect); + end; + except; // Silence sporadic EAccessViolation when reading DbObj.Connection.Parameters, found in uploaded reports + end; +end;} + + +{procedure TMainForm.DBtreeChange(Sender: TBaseVirtualTree; Node: PVirtualNode); +var + VT: TVirtualStringTree; +begin + // Resize "Size" column in dbtree to hold widest possible byte numbers without cutting text + VT := Sender as TVirtualStringTree; + if (VT.Header.Columns.Count >= 2) and (coVisible in VT.Header.Columns[1].Options) then + VT.Header.Columns[1].Width := TextWidth(VT.Canvas, FormatByteNumber(SIZE_MB*100)) + VT.TextMargin*2 + 8; +end;} + + +{procedure TMainForm.FormMouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; + var Handled: Boolean); +var + Control: TWinControl; + VT: TBaseVirtualTree; + PageControl: TPageControl; +begin + // Wheel scrolling only works in component which has focus. Help out by doing that by hand at least for any VirtualTree. + // See http://www.delphipraxis.net/viewtopic.php?p=1113607 + // TODO: Does not work when a SynMemo has focus, probably related to the broken solution of this issue: + // http://sourceforge.net/tracker/index.php?func=detail&aid=1574059&group_id=3221&atid=103221 + Control := FindVCLWindow(MousePos); + if (Control is TBaseVirtualTree) and (not Control.Focused) and PtInRect(Control.ClientRect, Control.ScreenToClient(MousePos)) then begin + VT := Control as TBaseVirtualTree; + VT.OffsetY := VT.OffsetY + (WheelDelta div 2); // Don't know why, but WheelDelta is twice as big as it normally appears + VT.UpdateScrollBars(True); + Handled := True; + end else if Control is TPageControl then begin + // Scroll tabs horizontally per mouse wheel + PageControl := Control as TPageControl; + if PageControl.MultiLine then begin + Handled := False; + end + else begin + PageControl.ScrollTabs(WheelDelta div WHEEL_DELTA); + if PageControl = PageControlMain then + FixQueryTabCloseButtons; + Handled := True; + end; + end else + Handled := False; +end;} + + +{procedure TMainForm.actDataResetSortingExecute(Sender: TObject); +begin + FDataGridSortItems.Clear; + InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); +end;} + + +{procedure TMainForm.WMCopyData(var Msg: TWMCopyData); +var + i: Integer; + Connection: TDBConnection; + Tab: TQueryTab; + ConnectionParams: TConnectionParameters; + FileNames: TStringList; + RunFrom: String; +begin + // Probably a second instance is posting its command line parameters here + if (Msg.CopyDataStruct.dwData = SecondInstMsgId) and (SecondInstMsgId <> 0) then begin + LogSQL(f_('Preventing second application instance - disabled in %s > %s > %s.', [_('Tools'), _('Preferences'), _('General')]), lcInfo); + ConnectionParams := nil; + ParseCommandLine(ParamBlobToStr(Msg.CopyDataStruct.lpData), ConnectionParams, FileNames, RunFrom); + if not RunQueryFiles(FileNames, nil, False) then begin + for i:=0 to FileNames.Count-1 do begin + Tab := GetOrCreateEmptyQueryTab(True); + Tab.LoadContents(FileNames[i], True, nil); + end; + end; + if ConnectionParams <> nil then + InitConnection(ConnectionParams, True, Connection); + end else + // Not the right message id + inherited; +end;} + + +{procedure TMainForm.CMStyleChanged(var Msg: TMessage); +begin + // Style theme applied, e.g. via preferences dialog + // Ensure SynMemo's have fitting colors + SetupSynEditors; +end;} + + +{procedure TMainForm.DefaultHandler(var Message); +begin + if TMessage(Message).Msg = SecondInstMsgId then begin + // A second instance asked for our handle. Post that into its message queue. + PostThreadMessage(TMessage(Message).WParam, SecondInstMsgId, Handle, 0); + end else + // Otherwise do what would happen without this overridden procedure + inherited; +end;} + + +{procedure TMainForm.actBlobAsTextExecute(Sender: TObject); +begin + // Activate displaying BLOBs as text data, ignoring possible weird effects in grid updates/inserts + DataGrid.InvalidateChildren(nil, True); +end;} + + +{procedure TMainForm.AnyGridScroll(Sender: TBaseVirtualTree; DeltaX, DeltaY: Integer); +begin + // A tree gets scrolled only when the mouse is over it - see FormMouseWheel + // Our home brewn cell editors do not reposition when the underlying tree scrolls. + // To avoid confusion, terminate editors then. + if Sender.IsEditing and (DeltaX=0) then + Sender.EndEditNode; +end;} + + +{procedure TMainForm.lblExplainProcessClick(Sender: TObject); +var + Tab: TQueryTab; + UsedDatabase: String; +begin + // Click on "Explain" link label, in process viewer + actNewQueryTabExecute(Sender); + Tab := QueryTabs[QueryTabs.Count-1]; + UsedDatabase := listProcesses.Text[listProcesses.FocusedNode, 3]; + if not UsedDatabase.IsEmpty then begin + Tab.Memo.Lines.Add('USE ' + ActiveConnection.QuoteIdent(UsedDatabase) + ';'); + end; + Tab.Memo.Lines.Add('EXPLAIN' + sLineBreak + SynMemoProcessView.Text + ';'); + Tab.TabSheet.Show; + actExecuteQueryExecute(Sender); +end;} + + +{procedure TMainForm.UpdateLineCharPanel; +var + x, y: Int64; + Grid: TVirtualStringTree; + AppendMsg: String; +begin + // Fill panel with "Line:Char" + x := -1; + y := -1; + AppendMsg := ''; + Grid := ActiveGrid; + if Assigned(Grid) and Grid.Focused then begin + if Assigned(Grid.FocusedNode) then + y := Grid.FocusedNode.Index+1; + x := Grid.FocusedColumn+1; + if Grid.SelectedCount > 1 then + AppendMsg := ' ('+FormatNumber(Grid.SelectedCount)+' sel)'; + end else if QueryTabs.HasActiveTab and QueryTabs.ActiveMemo.Focused then begin + x := QueryTabs.ActiveMemo.CaretX; + y := QueryTabs.ActiveMemo.CaretY; + AppendMsg := ' ('+FormatByteNumber(QueryTabs.ActiveMemo.GetTextLen)+')'; + end; + if (x > -1) and (y > -1) then begin + ShowStatusMsg('r'+FormatNumber(y)+' : c'+FormatNumber(x) + AppendMsg, 1) + end else + ShowStatusMsg('', 1); +end;} + +{procedure TMainForm.AnyGridStartOperation(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind); +begin + // Display status message on long running sort operations + if not MainFormCreated then begin + // Do nothing before form is not ready to process messages, what OperationRunning silently does. + // See issue #665 + Exit; + end; + if OperationKind = okSortTree then begin + ShowStatusMsg(_('Sorting grid nodes ...')); + FOperatingGrid := Sender; + OperationRunning(True); + end; +end;} + + +{procedure TMainForm.AnyGridEndOperation(Sender: TBaseVirtualTree; OperationKind: TVTOperationKind); +begin + // Reset status message after long running operations + if OperationKind = okSortTree then begin + ShowStatusMsg; + FOperatingGrid := nil; + OperationRunning(False); + end; +end;} + + +{procedure TMainForm.actCancelOperationExecute(Sender: TObject); +var + Killer: TDBConnection; + KillCommand: String; + Tab: TQueryTab; +begin + // Stop current operation (sorting grid or running user queries) + if FOperatingGrid <> nil then begin + FOperatingGrid.CancelOperation; + LogSQL(_('Sorting cancelled.')); + end; + for Tab in QueryTabs do begin + if Tab.QueryRunning then begin + Tab.ExecutionThread.Aborted := True; + Killer := ActiveConnection.Parameters.CreateConnection(Self); + Killer.Parameters := ActiveConnection.Parameters; + Killer.LogPrefix := _('Helper connection'); + Killer.OnLog := LogSQL; + try + Killer.Active := True; + KillCommand := Killer.GetSQLSpecifity(spKillQuery, [ActiveConnection.ThreadId]); + Killer.Query(KillCommand); + except + on E:EDbError do begin + MessageDialog(E.Message, mtError, [mbOK]); + end; + end; + Killer.Active := False; + Killer.Free; + end; + end; +end;} + + +{procedure TMainForm.OperationRunning(Runs: Boolean); +begin + if actCancelOperation.Enabled <> Runs then begin + actCancelOperation.ImageIndex := 159; + actCancelOperation.Enabled := Runs; + FOperationTicker := IfThen(Runs, GetTickCount, 0); + Application.ProcessMessages; + end else if Runs then begin + if (GetTickCount-FOperationTicker) > 250 then begin + // Signalize running operation + if actCancelOperation.ImageIndex = 159 then + actCancelOperation.ImageIndex := 160 + else + actCancelOperation.ImageIndex := 159; + Application.ProcessMessages; + FOperationTicker := GetTickCount; + end; + end; +end;} + + +{function TMainForm.GetEncodingByName(Name: String): TEncoding; +begin + Result := nil; + case FileEncodings.IndexOf(Name) of + 1: Result := TEncoding.Default; + 2: Result := TEncoding.GetEncoding(437); + 3: Result := TEncoding.Unicode; + 4: Result := TEncoding.BigEndianUnicode; + 5: Result := UTF8NoBOMEncoding; + 6: Result := TEncoding.UTF7; + 7: Result := TEncoding.UTF8; + end; +end;} + + +{function TMainForm.GetEncodingName(Encoding: TEncoding): String; +var + idx: Integer; +begin + if Encoding = TEncoding.Default then idx := 1 + else if (Encoding <> nil) and (Encoding.CodePage = 437) then idx := 2 + else if Encoding = TEncoding.Unicode then idx := 3 + else if Encoding = TEncoding.BigEndianUnicode then idx := 4 + else if Encoding = UTF8NoBOMEncoding then idx := 5 + else if Encoding = TEncoding.UTF7 then idx := 6 + else if Encoding = TEncoding.UTF8 then idx := 7 + else idx := 0; + Result := FileEncodings[idx]; +end;} + + +{function TMainForm.GetCharsetByEncoding(Encoding: TEncoding): String; +begin + Result := ''; + if Encoding = TEncoding.Default then begin + // Listing taken from http://forge.mysql.com/worklog/task.php?id=1349 + case GetACP of + 437: Result := 'cp850'; + 850: Result := 'cp850'; + 852: Result := 'cp852'; + 858: Result := 'cp850'; + 866: Result := 'cp866'; + 874: Result := 'tis620'; + 932: Result := 'cp932'; + 936: Result := 'gbk'; + 949: Result := 'euckr'; + 959: Result := 'big5'; + 1200: Result := 'utf16le'; + 1201: Result := 'utf16'; + 1250: Result := 'latin2'; + 1251: Result := 'cp1251'; + 1252: Result := 'latin1'; + 1253: Result := 'greek'; + 1254: Result := 'latin5'; + 1255: Result := 'hebrew'; + 1256: Result := 'cp1256'; + 1257: Result := 'cp1257'; + 10000: Result := 'macroman'; + 10001: Result := 'sjis'; + 10002: Result := 'big5'; + 10008: Result := 'gb2312'; + 10021: Result := 'tis620'; + 10029: Result := 'macce'; + 12001: Result := 'utf32'; + 20107: Result := 'swe7'; + 20127: Result := 'ascii'; + 20866: Result := 'koi8r'; + 20932: Result := 'ujis'; + 20936: Result := 'gb2312'; + 20949: Result := 'euckr'; + 21866: Result := 'koi8u'; + 28591: Result := 'latin1'; + 28592: Result := 'latin2'; + 28597: Result := 'greek'; + 28598: Result := 'hebrew'; + 28599: Result := 'latin5'; + 28603: Result := 'latin7'; + 28605: Result := 'latin9'; + 38598: Result := 'hebrew'; + 51932: Result := 'ujis'; + 51936: Result := 'gb2312'; + 51949: Result := 'euckr'; + 51950: Result := 'big5'; + 54936: Result := 'gb18030'; + 65001: Result := 'utf8'; + end; + end else if (Encoding <> nil) and (Encoding.CodePage = 437) then + Result := 'ascii' + else if Encoding = TEncoding.Unicode then + Result := 'utf16le' + else if Encoding = TEncoding.BigEndianUnicode then + Result := 'utf16' + else if Encoding = UTF8NoBOMEncoding then + Result := 'utf8' + else if Encoding = TEncoding.UTF7 then + Result := 'utf7' + else if Encoding = TEncoding.UTF8 then + Result := 'utf8' + // Auto-detection not supported here +end;} + + +{procedure TMainForm.treeQueryHelpersBeforeCellPaint(Sender: TBaseVirtualTree; TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; CellPaintMode: TVTCellPaintMode; CellRect: TRect; + var ContentRect: TRect); +var + Tab: TQueryTab; + History: TQueryHistory; +begin + // Paint green value bar in cell + if (Node.Parent.Index=TQueryTab.HelperNodeProfile) + and (Column=1) + and (Sender.GetNodeLevel(Node)=1) + then begin + Tab := QueryTabs.TabByControl(Sender); + if Tab <> nil then begin + Tab.QueryProfile.RecNo := Node.Index; + PaintColorBar(MakeFloat(Tab.QueryProfile.Col(Column)), Tab.MaxProfileTime, TargetCanvas, CellRect); + end; + end; + if (Sender.GetNodeLevel(Node)=2) + and (Column=1) + and (Node.Parent.Parent.Index=TQueryTab.HelperNodeHistory) then begin + Tab := QueryTabs.TabByControl(Sender); + if Tab <> nil then begin + History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory; + PaintColorBar(History[Node.Index].Duration, History.MaxDuration, TargetCanvas, CellRect); + end; + end; +end;} + + +{procedure TMainForm.treeQueryHelpersPaintText(Sender: TBaseVirtualTree; const TargetCanvas: TCanvas; + Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType); +var + History: TQueryHistory; + Tab: TQueryTab; +begin + // Paint text in datatype's color + if (Node.Parent.Index=TQueryTab.HelperNodeColumns) + and (Column=1) + and (Sender.GetNodeLevel(Node)=1) + and (ActiveDbObj.NodeType in [lntView, lntTable]) + then begin + TargetCanvas.Font.Color := DatatypeCategories[SelectedTableColumns[Node.Index].DataType.Category].Color; + end; + if (Sender.GetNodeLevel(Node)=2) + and (Node.Parent.Parent.Index=TQueryTab.HelperNodeHistory) + and (ActiveConnection <> nil) then begin + Tab := QueryTabs.TabByControl(Sender); + if Tab <> nil then begin + History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory; + if ActiveConnection.Database <> History[Node.Index].Database then + TargetCanvas.Font.Color := GetThemeColor(clGrayText); + end; + end; + + // If there is no value for bind variable, the font style is Italic and Underline + if (Sender.GetNodeLevel(Node)=1) + and (Column=1) + and (Node.Parent.Index=TQueryTab.HelperNodeBinding) then begin + Tab := QueryTabs.TabByControl(Sender); + if StrLen(PChar(Tab.ListBindParams.Items[Node.Index].Value)) = 0 then + TargetCanvas.Font.Style := [fsItalic]+[fsUnderline]; + + TargetCanvas.Font.Color := GetThemeColor(clGrayText); + + end; + +end;} + + +{procedure TMainForm.treeQueryHelpersResize(Sender: TObject); +var + Tree: TVirtualStringTree; +begin + // Resizing query helpers box: Keep second column at a minimum width. + Tree := Sender as TVirtualStringTree; + if Tree.Header.Columns.Count >= 2 then begin + // See https://github.com/HeidiSQL/HeidiSQL/issues/466 + // Column count may be 0 in an early stage of creating a new query tab + Tree.Header.Columns[1].Width := Max(Tree.Width div 3, 100); + end; +end;} + + +{procedure TMainForm.treeQueryHelpersDblClick(Sender: TObject); +var + m: TSynMemo; +begin + m := QueryTabs.ActiveMemo; + m.DragDrop(Sender, m.CaretX, m.CaretY); +end;} + + +{procedure TMainForm.treeQueryHelpersEditing(Sender: TBaseVirtualTree; + Node: PVirtualNode; Column: TColumnIndex; var Allowed: Boolean); +begin + // If current column is value of Bind Param, we allow editing + if (Column = 1) + and (Sender.FocusedNode.Parent.Index = TQueryTab.HelperNodeBinding) then begin + Allowed := True; + end +end;} + + +{procedure TMainForm.treeQueryHelpersFocusChanging(Sender: TBaseVirtualTree; OldNode, + NewNode: PVirtualNode; OldColumn, NewColumn: TColumnIndex; var Allowed: Boolean); +var + Tree: TVirtualStringTree; +begin + // Disable multi selection when snippet node is focused + Tree := Sender as TVirtualStringTree; + if not Assigned(NewNode) then + Exit; + if (Tree.GetNodeLevel(NewNode) = 0) or + (NewNode.Parent.Index=TQueryTab.HelperNodeSnippets) + then begin + Tree.ClearSelection; + Tree.TreeOptions.SelectionOptions := Tree.TreeOptions.SelectionOptions - [toMultiSelect] + end else + Tree.TreeOptions.SelectionOptions := Tree.TreeOptions.SelectionOptions + [toMultiSelect]; +end;} + + +{procedure TMainForm.treeQueryHelpersFreeNode(Sender: TBaseVirtualTree; + Node: PVirtualNode); +var + Tab: TQueryTab; +begin + // Free some memory, taken by probably big SQL query history items + if (Sender.GetNodeLevel(Node)=1) + and (Node.Parent.Index = TQueryTab.HelperNodeHistory) then begin + Tab := QueryTabs.TabByControl(Sender); + if Tab <> nil then begin + Tab.HistoryDays.Objects[Node.Index].Free; + Tab.HistoryDays.Delete(Node.Index); + end; + end; +end;} + + +{procedure TMainForm.treeQueryHelpersGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode; + Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean; var ImageIndex: TImageIndex); +begin + // Query helpers tree fetching node icon index + if not (Kind in [ikNormal, ikSelected]) then + Exit; + if Column <> 0 then + Exit; + case Sender.GetNodeLevel(Node) of + 0: case Node.Index of + TQueryTab.HelperNodeColumns: if (ActiveDbObj <> nil) and (ActiveDbObj.NodeType <> lntNone) then + ImageIndex := ActiveDbObj.ImageIndex + else + ImageIndex := 14; + TQueryTab.HelperNodeFunctions: ImageIndex := 13; + TQueryTab.HelperNodeKeywords: ImageIndex := 25; + TQueryTab.HelperNodeSnippets: ImageIndex := 51; + TQueryTab.HelperNodeHistory: ImageIndex := 149; + TQueryTab.HelperNodeProfile: ImageIndex := 145; + TQueryTab.HelperNodeBinding: ImageIndex := 119; + end; + 1: case Node.Parent.Index of + TQueryTab.HelperNodeColumns: ImageIndex := 42; + TQueryTab.HelperNodeFunctions: ImageIndex := 13; + TQueryTab.HelperNodeKeywords: ImageIndex := 25; + TQueryTab.HelperNodeSnippets: ImageIndex := 68; + TQueryTab.HelperNodeHistory: ImageIndex := 80; + TQueryTab.HelperNodeProfile: ImageIndex := 145; + TQueryTab.HelperNodeBinding: ImageIndex := 42; + end; + end; +end;} + + +{procedure TMainForm.treeQueryHelpersGetText(Sender: TBaseVirtualTree; Node: PVirtualNode; + Column: TColumnIndex; TextType: TVSTTextType; var CellText: string); +var + History: TQueryHistory; + Tab: TQueryTab; + Conn: TDBConnection; +begin + // Query helpers tree fetching node text + CellText := ''; + Tab := QueryTabs.TabByControl(Sender); + case Column of + 0: case Sender.GetNodeLevel(Node) of + 0: case Node.Index of + TQueryTab.HelperNodeColumns: begin + CellText := _('Columns'); + if ActiveDbObj <> nil then case ActiveDbObj.NodeType of + lntProcedure, lntFunction: CellText := f_('Parameters in %s', [ActiveDbObj.Name]); + lntTable, lntView: CellText := f_('Columns in %s', [ActiveDbObj.Name]); + end; + end; + TQueryTab.HelperNodeFunctions: CellText := _('SQL functions'); + TQueryTab.HelperNodeKeywords: CellText := _('SQL keywords'); + TQueryTab.HelperNodeSnippets: CellText := _('Snippets'); + TQueryTab.HelperNodeHistory: CellText := _('Query history'); + TQueryTab.HelperNodeProfile: begin + CellText := _('Query profile'); + if Assigned(Tab.QueryProfile) then + CellText := CellText + ' ('+FormatNumber(Tab.ProfileTime, 6)+'s)'; + end; + TQueryTab.HelperNodeBinding: begin + CellText := _('Bind parameters'); + if(Tab.BindParamsActivated) then + CellText := CellText + ' ('+FormatNumber(Tab.ListBindParams.Count)+')'; + end; + end; + 1: case Node.Parent.Index of + TQueryTab.HelperNodeColumns: begin + if ActiveDbObj <> nil then case ActiveDbObj.NodeType of + lntTable, lntView: + if SelectedTableColumns.Count > Integer(Node.Index) then + CellText := SelectedTableColumns[Node.Index].Name; + lntFunction, lntProcedure: + if Assigned(ActiveObjectEditor) then + CellText := TfrmRoutineEditor(ActiveObjectEditor).Parameters[Node.Index].Name; + end; + end; + TQueryTab.HelperNodeFunctions: begin + Conn := ActiveConnection; + if (Conn <> nil) and (Conn.SQLFunctions.Count > Integer(Node.Index)) then + CellText := Conn.SQLFunctions[Node.Index].Name; + end; + TQueryTab.HelperNodeKeywords: CellText := MySQLKeywords[Node.Index]; + TQueryTab.HelperNodeSnippets: CellText := IfThen(Node.Index < Cardinal(FSnippetFilenames.Count), FSnippetFilenames[Node.Index], ''); + TQueryTab.HelperNodeHistory: begin + CellText := Tab.HistoryDays[Node.Index]; + if CellText = DateToStr(Today) then + CellText := CellText + ', '+_('today') + else if CellText = DateToStr(Yesterday) then + CellText := CellText + ', '+_('yesterday'); + end; + TQueryTab.HelperNodeProfile: begin + if Assigned(Tab.QueryProfile) then begin + Tab.QueryProfile.RecNo := Node.Index; + CellText := Tab.QueryProfile.Col(Column); + end; + end; + TQueryTab.HelperNodeBinding: CellText := Tab.ListBindParams[Node.Index].Name; + end; + 2: case Node.Parent.Parent.Index of + TQueryTab.HelperNodeHistory: begin + History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory; + CellText := Copy(TimeToStr(History[Node.Index].Time), 1, 5)+': '+History[Node.Index].SQL; + end + else CellText := ''; // unused + end; + end; + 1: case Sender.GetNodeLevel(Node) of + 0: CellText := ''; + 1: case Node.Parent.Index of + TQueryTab.HelperNodeColumns: begin + if (ActiveDbObj <> nil) + and (ActiveDbObj.NodeType in [lntTable, lntView]) + and (SelectedTableColumns.Count > Integer(Node.Index)) then begin + CellText := SelectedTableColumns[Node.Index].DataType.Name; + end; + end; + TQueryTab.HelperNodeFunctions: begin + Conn := ActiveConnection; + if (Conn <> nil) and (Conn.SQLFunctions.Count > Integer(Node.Index)) then + CellText := Conn.SQLFunctions[Node.Index].Declaration; + end; + TQueryTab.HelperNodeProfile: begin + if Assigned(Tab.QueryProfile) then begin + Tab.QueryProfile.RecNo := Node.Index; + CellText := FormatNumber(Tab.QueryProfile.Col(Column))+'s'; + end; + end; + TQueryTab.HelperNodeBinding: begin + // If value is empty, display MsgBindParamNoValue + if StrLen(PChar(Tab.ListBindParams[Node.Index].Value))>0 then + CellText := Tab.ListBindParams[Node.Index].Value + else + CellText := _('Set value'); + end; + else CellText := ''; + end; + 2: case Node.Parent.Parent.Index of + TQueryTab.HelperNodeHistory: begin + History := Tab.HistoryDays.Objects[Node.Parent.Index] as TQueryHistory; + CellText := FormatNumber(History[Node.Index].Duration / 1000, 3)+'s'; + end; + end; + end; + end; +end;} + + +{procedure TMainForm.treeQueryHelpersInitNode(Sender: TBaseVirtualTree; ParentNode, + Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates); +begin + // Query helpers tree asking if plus/minus button should be displayed + case Sender.GetNodeLevel(Node) of + 0: begin + Include(InitialStates, ivsHasChildren); + if Node.Index = TQueryTab.HelperNodeProfile then + Node.CheckType := ctCheckbox; + if Node.Index = TQueryTab.HelperNodeBinding then + Node.CheckType := ctCheckbox; + end; + 1: begin + AppSettings.ResetPath; + if (Node.Parent.Index = TQueryTab.HelperNodeHistory) and AppSettings.ReadBool(asQueryHistoryEnabled) then + Include(InitialStates, ivsHasChildren); + end; + end; +end;} + + +{procedure TMainForm.treeQueryHelpersNewText(Sender: TBaseVirtualTree; + Node: PVirtualNode; Column: TColumnIndex; NewText: string); +var + Tree: TVirtualStringTree; + QueryTab: TQueryTab; +begin + Tree:= Sender as TVirtualStringTree; + QueryTab := QueryTabs.ActiveTab; + + // Save new param value + QueryTab.ListBindParams.Items[Sender.FocusedNode.Index].Value := NewText; + + Tree.RepaintNode(Node); +end;} + + +{procedure TMainForm.treeQueryHelpersNodeClick(Sender: TBaseVirtualTree; + const HitInfo: THitInfo); +var + Tree: TVirtualStringTree; +begin + Tree := Sender as TVirtualStringTree; + // If the column is clicked a parameter value, it goes directly into the edit mode + if (HitInfo.HitNode.Parent.Index = TQueryTab.HelperNodeBinding) and (HitInfo.HitColumn = 1) then + Tree.EditNode(Sender.FocusedNode,Sender.FocusedColumn); +end;} + + +{procedure TMainForm.treeQueryHelpersInitChildren(Sender: TBaseVirtualTree; Node: PVirtualNode; + var ChildCount: Cardinal); +var + QueryDay: String; + History: TQueryHistory; + Item: TQueryHistoryItem; + Tab: TQueryTab; + i: Integer; + Conn: TDBConnection; +begin + Tab := QueryTabs.TabByControl(Sender); + Conn := ActiveConnection; + case Sender.GetNodeLevel(Node) of + 0: case Node.Index of + TQueryTab.HelperNodeColumns: begin + ChildCount := 0; + if ActiveDbObj <> nil then case ActiveDbObj.NodeType of + lntTable, lntView: + ChildCount := SelectedTableColumns.Count; + lntFunction, lntProcedure: + if Assigned(ActiveObjectEditor) then + ChildCount := TfrmRoutineEditor(ActiveObjectEditor).Parameters.Count + else + ChildCount := 0; + end; + end; + TQueryTab.HelperNodeFunctions: ChildCount := ActiveConnection.SQLFunctions.Count; + TQueryTab.HelperNodeKeywords: ChildCount := MySQLKeywords.Count; + TQueryTab.HelperNodeSnippets: ChildCount := FSnippetFilenames.Count; + TQueryTab.HelperNodeHistory: begin + AppSettings.ResetPath; + if AppSettings.ReadBool(asQueryHistoryEnabled) and (Conn <> nil) then begin + // Find all unique days in history + if not Assigned(Tab.HistoryDays) then + Tab.HistoryDays := TStringList.Create; + Tab.HistoryDays.Clear; + History := TQueryHistory.Create(Conn.Parameters.SessionPath); + for Item in History do begin + QueryDay := DateToStr(Item.Time); + if Tab.HistoryDays.IndexOf(QueryDay) = -1 then + Tab.HistoryDays.Add(QueryDay); + end; + History.Free; + Tab.HistoryDays.CustomSort(StringListCompareAnythingDesc); + ChildCount := Tab.HistoryDays.Count; + end; + end; + TQueryTab.HelperNodeProfile: if not Assigned(Tab.QueryProfile) then ChildCount := 0 + else ChildCount := Tab.QueryProfile.RecordCount; + TQueryTab.HelperNodeBinding: ChildCount := Tab.ListBindParams.Count; + end; + 1: case Node.Parent.Index of + TQueryTab.HelperNodeHistory: begin + if Conn <> nil then begin + History := TQueryHistory.Create(Conn.Parameters.SessionPath); + Tab.HistoryDays.Objects[Node.Index] := History; + for i:=History.Count-1 downto 0 do begin + QueryDay := DateToStr(History[i].Time); + if QueryDay <> Tab.HistoryDays[Node.Index] then + History.Delete(i); + end; + ChildCount := History.Count; + end; + end; + else ChildCount := 0; + end; + end; +end;} + + +{procedure TMainForm.SetSnippetFilenames; +var + Files: TStringDynArray; + Snip: String; + i: Integer; +begin + // Refreshing list of snippet file names needs to refresh helper node too + if not Assigned(FSnippetFilenames) then + FSnippetFilenames := TStringList.Create; + FSnippetFilenames.Clear; + try + Files := TDirectory.GetFiles(AppSettings.DirnameSnippets, '*.sql'); + for i:=0 to Length(Files)-1 do begin + Snip := ExtractFilename(Files[i]); + Snip := Copy(Snip, 1, Length(Snip)-4); + FSnippetFilenames.Add(snip); + end; + FSnippetFilenames.Sort; + except + on E:Exception do begin + LogSQL(f_('Error with snippets directory: %s', [E.Message]), lcError); + end; + end; + RefreshHelperNode(TQueryTab.HelperNodeSnippets); +end;} + + +{procedure TMainForm.treeQueryHelpersChecking(Sender: TBaseVirtualTree; + Node: PVirtualNode; var NewState: TCheckState; var Allowed: Boolean); +var + Tab: TQueryTab; + Tree: TVirtualStringTree; +begin + Tree := Sender as TVirtualStringTree; + Tab := QueryTabs.TabByControl(Sender); + + case Sender.GetNodeLevel(Node) of + + 0: case Node.Index of + TQueryTab.HelperNodeBinding: begin + // Disallow checkbox clicking on "Bind parameters" when text too big + if NewState in CheckedStates then begin + if Tab.Memo.GetTextLen < SIZE_MB then begin + Allowed := True; + Tab.TimerLastChange.Enabled := False; + Tab.TimerLastChange.Enabled := True; + LogSQL('Bind parameters enabled', lcDebug); + end + else begin + Allowed := False; + MessageDialog(_('The query is too long to enable detection of bind parameters'), mtError, [mbOK]); + end; + end + else begin + Allowed := True; + Tab.ListBindParams.Clear; + NewState := csUncheckedNormal; + Tree.ResetNode(Node); + LogSQL('Bind parameters disabled', lcDebug); + end; + end; + end; + + end; + +end;} + + +{procedure TMainForm.treeQueryHelpersContextPopup(Sender: TObject; MousePos: TPoint; + var Handled: Boolean); +var + Tree: TVirtualStringTree; +begin + Tree := Sender as TVirtualStringTree; + menuQueryHelpersGenerateSelect.Enabled := False; + menuQueryHelpersGenerateInsert.Enabled := False; + menuQueryHelpersGenerateUpdate.Enabled := False; + menuQueryHelpersGenerateDelete.Enabled := False; + menuInsertAtCursor.Enabled := False; + menuLoadSnippet.Enabled := False; + menuDeleteSnippet.Enabled := False; + menuExplore.Enabled := False; + menuHelp.Enabled := False; + menuClearQueryHistory.Enabled := False; + + case Tree.GetNodeLevel(Tree.FocusedNode) of + 0: ; + 1: case Tree.FocusedNode.Parent.Index of + TQueryTab.HelperNodeColumns: begin + if ActiveDbObj.NodeType in [lntTable, lntView] then begin + menuQueryHelpersGenerateSelect.Enabled := True; + menuQueryHelpersGenerateInsert.Enabled := True; + menuQueryHelpersGenerateUpdate.Enabled := True; + menuQueryHelpersGenerateDelete.Enabled := True; + menuInsertAtCursor.Enabled := True; + end; + end; + TQueryTab.HelperNodeFunctions: begin + menuHelp.Enabled := True; + menuInsertAtCursor.Enabled := True; + end; + TQueryTab.HelperNodeKeywords: begin + menuHelp.Enabled := True; + menuInsertAtCursor.Enabled := True; + end; + TQueryTab.HelperNodeSnippets: begin + menuDeleteSnippet.Enabled := True; + menuInsertAtCursor.Enabled := True; + menuLoadSnippet.Enabled := True; + menuExplore.Enabled := True; + end; + TQueryTab.HelperNodeProfile: begin // Query profile + + end; + TQueryTab.HelperNodeHistory: + menuClearQueryHistory.Enabled := True; + end; + 2: case Tree.FocusedNode.Parent.Parent.Index of + TQueryTab.HelperNodeHistory: begin + menuClearQueryHistory.Enabled := True; + menuInsertAtCursor.Enabled := True; + end; + end; + end; +end;} + + +{procedure TMainForm.treeQueryHelpersCreateEditor(Sender: TBaseVirtualTree; + Node: PVirtualNode; Column: TColumnIndex; out EditLink: IVTEditLink); +var + InplaceEditor: TInplaceEditorLink; + VT: TVirtualStringTree; +begin + VT := Sender as TVirtualStringTree; + InplaceEditor := TInplaceEditorLink.Create(VT, True, nil); + InplaceEditor.ButtonVisible := true; + EditLink := InplaceEditor; +end;} + + +{procedure TMainForm.RefreshHelperNode(NodeIndex: Cardinal); +var + Tab: TQueryTab; + Node, Child: PVirtualNode; + OldStates: TVirtualNodeStates; + OldCheckState: TCheckState; + ExpandedChildren: TStringList; + Conn: TDBConnection; +begin + if not Assigned(QueryTabs) then + Exit; + Conn := ActiveConnection; + Tab := QueryTabs.ActiveTab; + if Tab = nil then + Exit; + Node := FindNode(Tab.treeHelpers, NodeIndex, nil); + // Store node + children states + OldStates := Node.States; + OldCheckState := Node.CheckState; + ExpandedChildren := TStringList.Create; + Child := Tab.treeHelpers.GetFirstChild(Node); + while Assigned(Child) do begin + if vsExpanded in Child.States then + ExpandedChildren.Add(IntToStr(Child.Index)); + Child := Tab.treeHelpers.GetNextSibling(Child); + end; + // Keep scroll offset + Tab.treeHelpers.BeginUpdate; + // Remove children and grandchildren + Tab.treeHelpers.ResetNode(Node); + // Restore old node + children states + Tab.treeHelpers.CheckState[Node] := OldCheckState; + Tab.treeHelpers.Expanded[Node] := vsExpanded in OldStates; + // Disable profiling when not on MySQL + if (NodeIndex = TQueryTab.HelperNodeProfile) and (Conn <> nil) and (not Conn.Parameters.IsAnyMySQL) then begin + Tab.treeHelpers.CheckState[Node] := csUncheckedNormal; + end; + // Do not check expansion state of children unless the parent node is expanded, to avoid + // initializing children when not required. Accesses registry items when doing so. + if Tab.treeHelpers.Expanded[Node] then begin + Child := Tab.treeHelpers.GetFirstChild(Node); + while Assigned(Child) do begin + Tab.treeHelpers.Expanded[Child] := ExpandedChildren.IndexOf(IntToStr(Child.Index)) > -1; + Child := Tab.treeHelpers.GetNextSibling(Child); + end; + end; + ExpandedChildren.Free; + Tab.treeHelpers.EndUpdate; +end;} + + +{procedure TMainForm.ApplicationEvents1Deactivate(Sender: TObject); +begin + // Force result tab balloon hint to disappear. Does not do so when mouse was moved too fast. + tabsetQueryMouseLeave(Sender); +end;} + + +{procedure TMainForm.ApplicationEvents1Idle(Sender: TObject; var Done: Boolean); +begin + if AppSettings.PortableMode + and (not AppSettings.PortableModeReadOnly) + and (FLastPortableSettingsSave < GetTickCount-60000) then begin + try + if AppSettings.Writes > FLastAppSettingsWrites then begin + if AppSettings.ExportSettings then + LogSQL(f_('Portable settings file written, with %d changes.', [AppSettings.Writes-FLastAppSettingsWrites]), lcDebug); + end; + FLastAppSettingsWrites := AppSettings.Writes; + FLastPortableSettingsSave := GetTickCount; + except + on E:Exception do + ErrorDialog(E.Message); + end; + Done := True; + end; + + // Sort list tables in idle time, so ListTables.TreeOptions.AutoSort does not crash the list + // when dropping a right-clicked database + if (PageControlMain.ActivePage = tabDatabase) and (not FListTablesSorted) then begin + ListTables.SortTree(ListTables.Header.SortColumn, ListTables.Header.SortDirection); + FListTablesSorted := True; + Done := True; + end; + + // Re-enable refresh action when application is idle + if (not actRefresh.Enabled) and (FRefreshActionDisabledAt < (GetTickCount - 1000)) then + begin + actRefresh.Enabled := True; + Done := True; + end; + +end;} + + +{procedure TMainForm.ApplicationEvents1ShortCut(var Msg: TWMKey; + var Handled: Boolean); +var + SendingControl: TComponent; + LastStart: Integer; + Edit: TCustomEdit; + Combo: TComboBox; + rx: TRegExpr; + TextMatches: Boolean; + PressedShortcut: TShortCut; + Act: TContainedAction; +begin + // Support for Ctrl+Backspace shortcut in edit + combobox controls + //LogSQL(msg.CharCode.ToString); + Handled := False; + if (Msg.CharCode = VK_BACK) and KeyPressed(VK_CONTROL) then begin + SendingControl := Screen.ActiveControl; + rx := TRegExpr.Create; + rx.Expression := '\b\W*\w+\W*$'; + if SendingControl is TCustomEdit then begin + Edit := TCustomEdit(SendingControl); + LastStart := Edit.SelStart; + TextMatches := rx.Exec(Copy(Edit.Text, 1, LastStart)); + if TextMatches then begin + Edit.SelStart := rx.MatchPos[0]-1; + Edit.SelLength := LastStart - Edit.SelStart; + // wParam=1 supports undo, in contrast to setting Edit.SelText + if IsWine then + Edit.SelText := '' + else + SendMessage(Edit.Handle, EM_REPLACESEL, 1, LongInt(PChar(''))); + end; + Handled := True; + end + else if SendingControl is TComboBox then begin + Combo := TComboBox(SendingControl); + LastStart := Combo.SelStart; + TextMatches := rx.Exec(Copy(Combo.Text, 1, LastStart)); + if TextMatches then begin + Combo.SelStart := rx.MatchPos[0]-1; + Combo.SelLength := LastStart - Combo.SelStart; + Combo.SelText := ''; + end; + Handled := True; + end; + LogSQL('Caught Ctrl+Backspace shortcut in '+SendingControl.ClassName+ + ', expression "'+rx.Expression+'" matched: '+TextMatches.ToInteger.ToString, + lcDebug); + rx.Free; + + end else begin + // Listen to certain shortcut(s) in other forms than mainform, which do not listen to ActionList + PressedShortcut := ShortCut(Msg.CharCode, KeyDataToShiftState(Msg.KeyData)); + for Act in ActionList1 do begin + if (Act = actSynEditCompletionPropose) + or (Act = actQueryFind) // Support find/replace on grid text editor + or (Act = actQueryFindAgain) + or (Act = actQueryReplace) + then begin + + if PressedShortcut = Act.ShortCut then begin + Act.Execute; + Handled := True; + Break; + end; + + end; + end; + + end; +end;} + +{procedure TMainForm.ApplicationDeActivate(Sender: TObject); +begin + // Prevent completion window from showing up after Alt-Tab. See issue #2640 + // and issue #3342 + // Does not work for some reason in TApplicationEvents.OnDeactivate + // Triggers an EAccessViolation when changing some VCL styles + try + SynCompletionProposal.Form.Enabled := False; + except + on E:EAccessViolation do + LogSQL(E.Message, lcError); + end; + // Gets activated again in SynCompletionProposalExecute +end;} + + +{procedure TMainForm.ApplicationShowHint(var HintStr: string; var CanShow: Boolean; var HintInfo: THintInfo); +var + MainTabIndex, QueryTabIndex, NewHideTimeout: integer; + pt: TPoint; + Conn: TDBConnection; + Editor: TSynMemo; +begin + if HintInfo.HintControl = PageControlMain then begin + // Show full filename in tab hint. See issue #3527 + // Code taken from http://www.delphipraxis.net/97988-tabsheet-hint-funktioniert-nicht.html + pt := PageControlMain.ScreenToClient(Mouse.CursorPos); + MainTabIndex := GetMainTabAt(pt.X, pt.Y); + QueryTabIndex := MainTabIndex - tabQuery.PageIndex; + if (QueryTabIndex >= 0) and (QueryTabIndex < QueryTabs.Count) then begin + HintStr := QueryTabs[QueryTabIndex].MemoFilename; + end + else if MainTabIndex = tabHost.TabIndex then begin + Conn := ActiveConnection; + HintStr := Conn.Parameters.Hostname; + if Conn.Parameters.IsAnySQLite then + HintStr := StringReplace(HintStr, DELIM, SLineBreak, [rfReplaceAll]); + end; + HintInfo.ReshowTimeout := 1000; + SetHintFontByControl; + end + else if HintInfo.HintControl is TSynMemo then begin + // Token hint displaying through SynEdit's OnTokenHint event + Editor := TSynMemo(HintInfo.HintControl); + SetHintFontByControl(Editor); + NewHideTimeout := Min(Length(HintStr) * 100, 60*1000); + if NewHideTimeout > HintInfo.HideTimeout then + HintInfo.HideTimeout := NewHideTimeout; + end + else begin + // Probably reset hint font + SetHintFontByControl; + end; +end;} + + +{procedure TMainForm.actToggleCommentExecute(Sender: TObject); +var + Editor: TSynMemo; + rx: TRegExpr; + Sel: TStringList; + i: Integer; + IsComment: Boolean; +begin + // Un/comment selected SQL + Editor := ActiveSynMemo(False); + Editor.UndoList.AddGroupBreak; + rx := TRegExpr.Create; + rx.Expression := '^(\s*)(\-\- |#)?(.*)$'; + if not Editor.SelAvail then begin + rx.Exec(Editor.LineText); + if rx.MatchLen[2] > 0 then begin + Editor.LineText := rx.Match[1] + rx.Match[3]; + Editor.CaretX := Editor.CaretX - rx.MatchLen[2]; + end else begin + Editor.LineText := '-- '+Editor.LineText; + Editor.CaretX := Editor.CaretX + 3; + end; + end else begin + Sel := Explode(CRLF, Editor.SelText); + IsComment := False; + for i:=0 to Sel.Count-1 do begin + rx.Exec(Sel[i]); + if i = 0 then + IsComment := rx.MatchLen[2] > 0; + if IsComment then + Sel[i] := rx.Match[1] + rx.Match[3] + else + Sel[i] := '-- '+Sel[i]; + end; + Editor.SelText := Implode(CRLF, Sel); + if Assigned(Editor.OnStatusChange) then + Editor.OnStatusChange(Editor, [scCaretX]); + end; +end;} + + +{procedure TMainForm.EnableProgress(MaxValue: Integer); +begin + // Initialize progres bar and button + SetProgressState(pbsNormal); + ProgressBarStatus.Visible := True and (not IsWine); + SetProgressPosition(0); + ProgressBarStatus.Max := MaxValue; +end;} + + +{procedure TMainForm.DisableProgress; +begin + // Hide global progress bar + SetProgressPosition(0); + ProgressBarStatus.Hide; + if Assigned(TaskBarList3) then + TaskBarList3.SetProgressState(Handle, 0); +end;} + + +{procedure TMainForm.SetProgressPosition(Value: Integer); +begin + // Advance progress bar and task progress position + try + ProgressBarStatus.Position := Value; + except + // Silence "Floating point division by zero." - see https://www.heidisql.com/forum.php?t=26218 + on E:EZeroDivide do; + end; + ProgressBarStatus.Repaint; + if Assigned(TaskBarList3) then + TaskBarList3.SetProgressValue(Handle, Value, ProgressBarStatus.Max); +end;} + + +{procedure TMainForm.ProgressStep; +begin + SetProgressPosition(ProgressBarStatus.Position+1); +end;} + + +{procedure TMainForm.SetProgressState(State: TProgressbarState); +var + Flag: Integer; +begin + // Set error or pause state in progress bar or task button + ProgressBarStatus.State := State; + ProgressBarStatus.Repaint; + if Assigned(TaskBarList3) then begin + case State of + pbsNormal: Flag := 2; + pbsError: Flag := 4; + pbsPaused: Flag := 8; + else Flag := 0; + end; + TaskBarList3.SetProgressState(Handle, Flag); + end; +end;} + + +{procedure TMainForm.TaskDialogHyperLinkClicked(Sender: TObject); +begin + // Used by hyperlinks in helpers.MessageDialog() + if Sender is TTaskDialog then + ShellExec(TTaskDialog(Sender).URL); +end;} + + +{function TMainForm.HasDonated(ForceCheck: Boolean): TThreeStateBoolean; +var + Email, CheckResult: String; + rx: TRegExpr; + CheckWebpage: THttpDownload; +begin + Screen.Cursor := crHourGlass; + if (FHasDonatedDatabaseCheck = nbUnset) or (ForceCheck) then begin + Email := AppSettings.ReadString(asDonatedEmail); + if Email = '' then begin + // Nothing to check, we know this is not valid + FHasDonatedDatabaseCheck := nbFalse; + end else begin + // Check heidisql.com/hasdonated.php?email=... + // FHasDonatedDatabaseCheck + // = 0 : No check yet done + // = 1 : Not a donor + // = 2 : Valid donor + rx := TRegExpr.Create; + CheckWebpage := THttpDownload.Create(MainForm); + CheckWebpage.URL := APPDOMAIN + 'hasdonated.php?email='+EncodeURLParam(Email); + try + CheckWebpage.SendRequest(''); + CheckResult := CheckWebpage.LastContent; + LogSQL('HTTP response: "'+CheckResult+'"', lcDebug); + rx.Expression := '^\d'; + if rx.Exec(CheckResult) then begin + if CheckResult = '0' then + FHasDonatedDatabaseCheck := nbFalse + else + FHasDonatedDatabaseCheck := nbTrue; + end; + except + on E:Exception do begin + LogSQL(E.Message + sLineBreak + 'HTTP response: "'+CheckResult+'"', lcError); + FHasDonatedDatabaseCheck := nbUnset; // Could have been set before, when ForceCheck=true + end; + end; + CheckWebpage.Free; + rx.Free; + end; + end; + Result := FHasDonatedDatabaseCheck; + Screen.Cursor := crDefault; +end;} + + +{procedure TMainForm.actPreviousResultExecute(Sender: TObject); +var + Tab: TQueryTab; +begin + // Go back to the result tab left to the active one + Tab := QueryTabs.ActiveTab; + if Tab <> nil then begin + if Tab.tabsetQuery.TabIndex > 0 then + Tab.tabsetQuery.SelectNext(False) + else + MessageBeep(MB_ICONEXCLAMATION); + end; +end;} + + +{procedure TMainForm.actNextResultExecute(Sender: TObject); +var + Tab: TQueryTab; +begin + // Advance to the next result tab + Tab := QueryTabs.ActiveTab; + if Tab <> nil then begin + if Tab.tabsetQuery.TabIndex < Tab.tabsetQuery.Tabs.Count-1 then + Tab.tabsetQuery.SelectNext(True) + else + MessageBeep(MB_ICONEXCLAMATION); + end; +end;} + + +{function TMainForm.SelectedTableFocusedColumn: TTableColumn; +var + Col: TTableColumn; +begin + // Return column object of focused data grid column. + // DataGrid columns can be deselected by user, but SelectedTableColumns has all of them, + // so we cannot access them by their 0-based number. Instead, search by name/caption. + Result := nil; + for Col in SelectedTableColumns do begin + if Col.Name = DataGrid.Header.Columns[DataGrid.FocusedColumn].Text then begin + Result := Col; + Break; + end; + end; +end;} + + + + +{ TQueryTab } + + +{constructor TQueryTab.Create; +begin + // Creation of a new main query tab + DirectoryWatch := TDirectoryWatch.Create; + DirectoryWatch.WatchSubTree := False; + DirectoryWatch.OnNotify := DirectoryWatchNotify; + DirectoryWatch.OnError := DirectoryWatchErrorHandler; + // Do not trigger useless file deletion messages, see issue #2948 + DirectoryWatch.WatchActions := DirectoryWatch.WatchActions - [waRemoved]; + // Do not trigger file access. See https://www.heidisql.com/forum.php?t=15500 + DirectoryWatch.WatchOptions := DirectoryWatch.WatchOptions - [woLastAccess]; + // Timer which postpones calling waModified event code until buffers have been saved + MemofileModifiedTimer := TTimer.Create(Memo); + MemofileModifiedTimer.Interval := 1000; + MemofileModifiedTimer.Enabled := False; + MemofileModifiedTimer.OnTimer := MemofileModifiedTimerNotify; + LastSaveTime := 0; + FLastChange := 0; + TimerLastChange := TTimer.Create(Self); + TimerLastChange.Enabled := True; + TimerLastChange.OnTimer := TimerLastChangeOnTimer; + // Contain 2 columns of String : Params & Values + ListBindParams := TListBindParam.Create; + // Update status bar every second while query runs + TimerStatusUpdate := TTimer.Create(Self); + TimerStatusUpdate.Enabled := False; + TimerStatusUpdate.Interval := 100; + TimerStatusUpdate.OnTimer := TimerStatusUpdateOnTimer; + FFileEncoding := 'UTF-8'; +end;} + + +{destructor TQueryTab.Destroy; +begin + ResultTabs.Clear; + DirectoryWatch.Free; + ListBindParams.Free; + TimerLastChange.Free; + TimerStatusUpdate.Free; +end;} + + +{function TQueryTab.GetActiveResultTab: TResultTab; +var + idx: Integer; +begin + Result := nil; + idx := tabsetQuery.TabIndex; + if (idx > -1) and (idx < ResultTabs.Count) then + Result := ResultTabs[idx]; +end;} + + +{procedure TQueryTab.DirectoryWatchNotify(const Sender: TObject; const Action: TWatchAction; const FileName: string); +var + IsCurrentFile: Boolean; +begin + // Notification about file changes in loaded file's directory + + if FDirectoryWatchNotficationRunning then + Exit; + FDirectoryWatchNotficationRunning := True; + + IsCurrentFile := DirectoryWatch.Directory + FileName = MemoFilename; + case Action of + waRemoved: + if IsCurrentFile + and (MessageDialog(_('Close file and query tab?'), f_('File was deleted from outside: %s', [MemoFilename]), mtConfirmation, [mbYes, mbCancel]) = mrYes) then begin + Mainform.actClearQueryEditor.Execute; + if Mainform.IsQueryTab(TabSheet.PageIndex, False) then + Mainform.CloseQueryTab(TabSheet.PageIndex); + end; + + waModified: + if IsCurrentFile and (LastSaveTime < GetTickCount-MemofileModifiedTimer.Interval) then begin + MemofileModifiedTimer.Enabled := False; + MemofileModifiedTimer.Enabled := True; + end; + + waRenamedOld: + if IsCurrentFile then + MemoFileRenamed := True; + + waRenamedNew: + if (not IsCurrentFile) and (MemoFilename <> '') and MemoFileRenamed then begin + MemoFilename := DirectoryWatch.Directory + FileName; + MemoFileRenamed := False; + end; + + end; + FDirectoryWatchNotficationRunning := False; +end;} + + +{procedure TQueryTab.DirectoryWatchErrorHandler(const Sender: TObject; const ErrorCode: Integer; const ErrorMessage: string); +begin + MainForm.LogSQL(Format('File watcher (%d): %s', [ErrorCode, ErrorMessage]), lcError); +end;} + + +{procedure TQueryTab.MemofileModifiedTimerNotify(Sender: TObject); +var + OldTopLine: Integer; + OldCursor: TBufferCoord; +begin + (Sender as TTimer).Enabled := False; + if FDirectoryWatchNotficationRunning then + Exit; + FDirectoryWatchNotficationRunning := True; + if MessageDialog(_('Reload file?'), f_('File was modified from outside: %s', [MemoFilename]), mtConfirmation, [mbYes, mbCancel]) = mrYes then begin + OldCursor := Memo.CaretXY; + OldTopLine := Memo.TopLine; + LoadContents(MemoFilename, True, nil); + Memo.CaretXY := OldCursor; + Memo.TopLine := OldTopLine; + end; + FDirectoryWatchNotficationRunning := False; +end;} + + +{function TQueryTab.LoadContents(Filepath: String; ReplaceContent: Boolean; Encoding: TEncoding): Boolean; +var + Content: String; + Filesize: Int64; + LineBreaks: TLineBreaks; + LoadSuccess: Boolean; +begin + // Load file and add that to the undo-history of SynEdit. + // Normally we would do a simple SynMemo.Lines.LoadFromFile but + // this would prevent SynEdit from adding this step to the undo-history + // so we have to do it by replacing the SelText property + Result := False; + Screen.Cursor := crHourGlass; + Filesize := _GetFileSize(Filepath); + LoadSuccess := False; + MainForm.LogSQL(f_('Loading file "%s" (%s) into query tab #%d', [Filepath, FormatByteNumber(Filesize), Number]), lcInfo); + try + Content := ReadTextfile(Filepath, Encoding); + LoadSuccess := True; + except on E:Exception do + // File does not exist, is locked or broken + ErrorDialog(E.message + sLineBreak + sLineBreak + Filepath); + end; + + if LoadSuccess then begin + if Pos(AppSettings.DirnameSnippets, Filepath) = 0 then + MainForm.AddOrRemoveFromQueryLoadHistory(Filepath, True, True); + Memo.UndoList.AddGroupBreak; + LineBreaks := ScanLineBreaks(Content); + if ReplaceContent then begin + Memo.Clear; + MemoLineBreaks := LineBreaks; + end else begin + if (MemoLineBreaks <> lbsNone) and (MemoLineBreaks <> LineBreaks) then + MemoLineBreaks := lbsMixed + else + MemoLineBreaks := LineBreaks; + end; + if MemoLineBreaks = lbsMixed then + MessageDialog(_('This file contains mixed linebreaks. They have been converted to Windows linebreaks (CR+LF).'), mtInformation, [mbOK]); + + if ReplaceContent then + Memo.Text := Content + else + Memo.SelText := Content; + Memo.SelStart := Memo.SelEnd; + Memo.Modified := False; + MemoFilename := Filepath; + FileEncoding := MainForm.GetEncodingName(Encoding); + //showmessage(FileEncoding); + Result := True; + end; + + Screen.Cursor := crDefault; +end;} + + +{procedure TQueryTab.SaveContents(Filename: String; OnlySelection: Boolean); +var + Text, LB, FileDir: String; +begin + Screen.Cursor := crHourGlass; + MainForm.ShowStatusMsg(_('Saving file ...')); + if OnlySelection then + Text := Memo.SelText + else + Text := Memo.Text; + LB := GetLineBreak(MemoLineBreaks); + if LB <> CRLF then + Text := StringReplace(Text, CRLF, LB, [rfReplaceAll]); + try + FileDir := ExtractFilePath(Filename); + if not DirectoryExists(FileDir) then + ForceDirectories(FileDir); + SaveUnicodeFile(Filename, Text, MainForm.GetEncodingByName(FFileEncoding)); + MemoFilename := Filename; + Memo.Modified := False; + LastSaveTime := GetTickCount; + Screen.Cursor := crDefault; + except + on E:Exception do begin + Screen.Cursor := crDefault; + ErrorDialog(E.Message); + end; + end; + MainForm.ShowStatusMsg; +end;} + + +{class function TQueryTab.GenerateUid: String; +begin + // Generate fresh unique id for a new tab + // Keep it readable by using the date with milliseconds + DateTimeToString(Result, 'yyyy-mm-dd_hh-nn-ss-zzz', Now); +end;} + + +{function TQueryTab.MemoBackupFilename: String; +begin + // Return filename for auto-backup feature + if (MemoFilename <> '') and (not Memo.Modified) then begin + Result := ''; + end else begin + Result := IncludeTrailingBackslash(AppSettings.DirnameBackups) + + ValidFilename(Format(BACKUP_FILEPATTERN, [Uid])) + ; + end; +end;} + + +{procedure TQueryTab.BackupUnsavedContent; +var + LastFileBackup: TDateTime; +begin + // Fired before closing application, and also timer controlled + + // Check if content is a user stored file and if it has modified content: + if MemoBackupFilename.IsEmpty then + Exit; + + // Check if existing backup file is up-to-date: + if FileExists(MemoBackupFilename) then begin + FileAge(MemoBackupFilename, LastFileBackup); + if LastFileBackup > FLastChange then + Exit; + end; + + if Memo.GetTextLen = 0 then begin + // If memo is empty, remove backup file + if FileExists(MemoBackupFilename) then begin + if not DeleteFile(MemoBackupFilename) then begin + MainForm.LogSQL('Could not remove empty backup file "'+MemoBackupFilename+'"', lcError); + end; + end; + end else begin + if Memo.GetTextLen < SIZE_MB*10 then begin + MainForm.LogSQL('Saving backup file to "'+MemoBackupFilename+'"...', lcDebug); + MainForm.ShowStatusMsg(_('Saving backup file...')); + SaveUnicodeFile(MemoBackupFilename, Memo.Text, UTF8NoBOMEncoding); + end else begin + MainForm.LogSQL('Unsaved tab contents too large (> 10M) for creating a backup.', lcDebug); + end; + end; + MainForm.ShowStatusMsg(''); +end;} + + +{function TQueryTab.GetBindParamsActivated: Boolean; +var + Node: PVirtualNode; +begin + // Return state of bind params checkbox + Result := False; + Node := FindNode(treeHelpers, TQueryTab.HelperNodeBinding, nil); + if Assigned(Node) then + Result := treeHelpers.CheckState[Node] in CheckedStates; +end;} + + +{procedure TQueryTab.SetBindParamsActivated(Value: Boolean); +var + Node: PVirtualNode; +begin + // Check bind params checkbox + Node := FindNode(treeHelpers, TQueryTab.HelperNodeBinding, nil); + if Value then + treeHelpers.CheckState[Node] := csCheckedNormal + else + treeHelpers.CheckState[Node] := csUncheckedNormal; +end;} + + +{procedure TQueryTab.SetErrorLine(Value: Integer); +begin + if Value <> FErrorLine then begin + FErrorLine := Value; + Memo.Repaint; + end; +end;} + + +{procedure TQueryTab.SetMemoFilename(Value: String); +begin + FMemoFilename := Value; + MainForm.SetTabCaption(TabSheet.PageIndex, ExtractFilename(FMemoFilename)); + MainForm.ValidateQueryControls(Self); + if (FMemoFilename <> '') and FileExists(FMemoFilename) then begin + DirectoryWatch.Directory := ExtractFilePath(FMemoFilename); + DirectoryWatch.Start; + end else + DirectoryWatch.Stop; +end;} + + +{procedure TQueryTab.SetQueryRunning(Value: Boolean); +begin + // Marker for query tab that it is currently executing and waiting for a query + FQueryRunning := Value; + TimerStatusUpdate.Enabled := Value; +end;} + + +{procedure TQueryTab.TimerLastChangeOnTimer(Sender: TObject); +var + rx: TRegExpr; + BindParam: TBindParam; + ParamCountBefore: Integer; + Node : PVirtualNode; + ParamName: String; + ParamFound: Boolean; +begin + TimerLastChange.Enabled := False; + + if not BindParamsActivated then + Exit; + if Memo.GetTextLen > SIZE_MB then begin + MessageDialog(_('The query is too long to enable detection of bind parameters'), mtError, [mbOK]); + Node := FindNode(treeHelpers, TQueryTab.HelperNodeBinding, nil); + treeHelpers.CheckState[Node] := csUncheckedNormal; + Exit; + end; + + MainForm.LogSQL('Bind parameter detection...', lcInfo); + ParamCountBefore := ListBindParams.Count; + + // Check current Query memo to find all parameters with regular expression ( :params ) + rx := TRegExpr.Create; + // Can't use (?<!\w):\w+ with actuall unit so this is an other solution + // Don't use ^:\w+|\W:[^\W:]\w+ because it detect IP v6 + rx.Expression := '([^:\w]|^):\w+'; + if rx.Exec(Memo.Text) then while true do begin + + // Don't get first char if it's not ':' because RegEx contain \W (A non-word character) before ':' + ParamName := Copy(rx.Match[0], Pos(':',rx.Match[0]), Length(rx.Match[0])-Pos(':',rx.Match[0])+1); + + // Check if parameter already exists + ParamFound := False; + for BindParam in ListBindParams do begin + if BindParam.Name = ParamName then begin + BindParam.Keep := True; + ParamFound := True; + end; + end; + + // If not exists, prepare and add new TBindParam + if not ParamFound then begin + BindParam := TBindParam.Create; + BindParam.Name := ParamName; + BindParam.Value := ''; + BindParam.Keep := True; + ListBindParams.Add(BindParam); + end; + + // Try to find next parameter + if not rx.ExecNext then + break; + end; + + rx.Free; + + // Sort list ascending + ListBindParams.Sort; + // Delete all parameters where variable is to False + ListBindParams.CleanToKeep; + + // Refresh bind param tree node, so it displays its children. Expand it when it has params for the first time. + MainForm.RefreshHelperNode(TQueryTab.HelperNodeBinding); + if (ParamCountBefore=0) and (ListBindParams.Count>0) then begin + Node := FindNode(treeHelpers, TQueryTab.HelperNodeBinding, nil); + treeHelpers.Expanded[Node] := True; + end; + + MainForm.LogSQL(IntToStr(ListBindParams.Count) + ' bind parameters found.', lcDebug); +end;} + + +{procedure TQueryTab.TimerStatusUpdateOnTimer(Sender: TObject); +var + Msg, ElapsedMsg: String; + Elapsed: Int64; +begin + // Update status bar every second with elapsed time + Msg := _('query')+' #' + FormatNumber(ExecutionThread.BatchPosition+1); + if ExecutionThread.QueriesInPacket > 1 then + Msg := f_('queries #%s to #%s', [FormatNumber(ExecutionThread.BatchPosition+1), FormatNumber(ExecutionThread.BatchPosition+ExecutionThread.QueriesInPacket)]); + try + Elapsed := MilliSecondsBetween(ExecutionThread.QueryStartedAt, Now); + ElapsedMsg := FormatTimeNumber(Elapsed/1000, True); + MainForm.ShowStatusMsg(ElapsedMsg + ': ' + f_('Executing %s of %s ...', [Msg, FormatNumber(ExecutionThread.Batch.Count)])); + except; + // Some crashes here, probably when accessing the no longer running thread. + // See https://www.heidisql.com/forum.php?t=25418#p25484 + // See issue https://github.com/HeidiSQL/HeidiSQL/issues/490 + end; +end;} + + +{ TQueryTabList } + +{function TQueryTabList.ActiveTab: TQueryTab; +var + idx: Integer; + FixedTab: TQueryTab; +begin + // Return active tab + Result := nil; + if Self.Count < 1 then + Exit; + FixedTab := Self[0]; + idx := FixedTab.TabSheet.PageControl.ActivePageIndex - FixedTab.TabSheet.PageIndex; + if (idx >= 0) and (idx < Self.Count) then + Result := Self[idx]; +end;} + + +{function TQueryTabList.HasActiveTab: Boolean; +begin + Result := ActiveTab <> nil; +end;} + + +{function TQueryTabList.ActiveMemo: TSynMemo; +var + Tab: TQueryTab; +begin + // Return current query memo + Result := nil; + Tab := ActiveTab; + if Assigned(Tab) then + Result := Tab.Memo; +end;} + + +{function TQueryTabList.ActiveHelpersTree: TVirtualStringTree; +var + Tab: TQueryTab; +begin + // Return current query helpers tree + Result := nil; + Tab := ActiveTab; + if Assigned(Tab) then + Result := Tab.treeHelpers; +end;} + + +{function TQueryTabList.TabByNumber(Number: Integer): TQueryTab; +var + Tab: TQueryTab; +begin + // Find right query tab + Result := nil; + for Tab in Self do begin + if Tab.Number = Number then begin + Result := Tab; + break; + end; + end; +end;} + + +{function TQueryTabList.TabByControl(Control: TWinControl): TQueryTab; +var + Tab: TQueryTab; +begin + // Find query tab where passed control resides + // Supports only most important controls in the upper area, excluding tab close button and result grid + Result := nil; + for Tab in Self do begin + if (Control = Tab.TabSheet) + or (Control = Tab.pnlMemo) or (Control = Tab.Memo) + or (Control = Tab.pnlHelpers) or (Control = Tab.filterHelpers) or (Control = Tab.treeHelpers) + or (Control = Tab.tabsetQuery) + then begin + Result := Tab; + Break; + end; + end; +end;} + + + + + +{ TResultTab } + +{constructor TResultTab.Create(AOwner: TQueryTab); +var + QueryTab: TQueryTab; + OrgGrid: TVirtualStringTree; +begin + inherited Create; + QueryTab := AOwner; + OrgGrid := Mainform.QueryGrid; + Grid := TVirtualStringTree.Create(QueryTab.TabSheet); + Grid.Parent := QueryTab.TabSheet; + Grid.Tag := OrgGrid.Tag; + Grid.BorderStyle := OrgGrid.BorderStyle; + Grid.Align := OrgGrid.Align; + Grid.Visible := False; + Grid.TreeOptions := OrgGrid.TreeOptions; + Grid.PopupMenu := OrgGrid.PopupMenu; + Grid.LineStyle := OrgGrid.LineStyle; + Grid.EditDelay := OrgGrid.EditDelay; + Grid.Font.Assign(OrgGrid.Font); + Grid.Header.Options := OrgGrid.Header.Options; + Grid.Header.PopupMenu := OrgGrid.Header.PopupMenu; + Grid.Header.ParentFont := OrgGrid.Header.ParentFont; + Grid.Header.Images := OrgGrid.Header.Images; + Grid.WantTabs := OrgGrid.WantTabs; + Grid.AutoScrollDelay := OrgGrid.AutoScrollDelay; + // Apply events - keep in alphabetical order for overview reasons + Grid.OnAdvancedHeaderDraw := OrgGrid.OnAdvancedHeaderDraw; + Grid.OnAfterCellPaint := OrgGrid.OnAfterCellPaint; + Grid.OnAfterPaint := OrgGrid.OnAfterPaint; + Grid.OnBeforeCellPaint := OrgGrid.OnBeforeCellPaint; + Grid.OnChange := OrgGrid.OnChange; + Grid.OnCreateEditor := OrgGrid.OnCreateEditor; + Grid.OnCompareNodes := OrgGrid.OnCompareNodes; + Grid.OnEditCancelled := OrgGrid.OnEditCancelled; + Grid.OnEdited := OrgGrid.OnEdited; + Grid.OnEditing := OrgGrid.OnEditing; + Grid.OnEndOperation := OrgGrid.OnEndOperation; + Grid.OnEnter := OrgGrid.OnEnter; + Grid.OnExit := OrgGrid.OnExit; + Grid.OnFocusChanged := OrgGrid.OnFocusChanged; + Grid.OnFocusChanging := OrgGrid.OnFocusChanging; + Grid.OnGetNodeDataSize := OrgGrid.OnGetNodeDataSize; + Grid.OnGetText := OrgGrid.OnGetText; + Grid.OnHeaderClick := OrgGrid.OnHeaderClick; + Grid.OnHeaderDrawQueryElements := OrgGrid.OnHeaderDrawQueryElements; + Grid.OnInitNode := OrgGrid.OnInitNode; + Grid.OnKeyDown := OrgGrid.OnKeyDown; + Grid.OnMouseUp := OrgGrid.OnMouseUp; + Grid.OnMouseWheel := OrgGrid.OnMouseWheel; + Grid.OnNewText := OrgGrid.OnNewText; + Grid.OnPaintText := OrgGrid.OnPaintText; + Grid.OnStartOperation := OrgGrid.OnStartOperation; + FixVT(Grid, AppSettings.ReadInt(asGridRowLineCount)); + FTabIndex := QueryTab.ResultTabs.Count; // Will be 0 for the first one, even if we're already creating the first one here! +end;} + +{destructor TResultTab.Destroy; +begin + Results.Free; + Grid.EndEditNode; + // The grid itself is owned by the parent tabsheet, free it only if the tabsheet is not being closed + if not (csDestroying in Grid.ComponentState) then + Grid.Free; + inherited; +end;} + + +{ TQueryHistory } + +{constructor TQueryHistory.Create(SessionPath: String); +var + ValueNames: TStringList; + i, j, p: Integer; + Raw: String; + Item: TQueryHistoryItem; +begin + inherited Create(TQueryHistoryItemComparer.Create, True); + AppSettings.SessionPath := SessionPath + '\' + REGKEY_QUERYHISTORY; + ValueNames := AppSettings.GetValueNames; + for i:=0 to ValueNames.Count-1 do begin + j := StrToIntDef(ValueNames[i], -1); + // Prevent from running into serious errors when registry has some non-numeric value + if j<>-1 then begin + Item := TQueryHistoryItem.Create; + try + Item.RegValue := j; + Raw := AppSettings.ReadString(ValueNames[i]); + p := Pos(DELIM, Raw); + Item.Time := StrToDateTime(Copy(Raw, 1, p-1)); + System.Delete(Raw, 1, p); + p := Pos(DELIM, Raw); + Item.Database := Copy(Raw, 1, p-1); + System.Delete(Raw, 1, p); + p := Pos(DELIM, Raw); + Item.Duration := StrToIntDef(Copy(Raw, 1, p-1), 0); + FMaxDuration := Max(FMaxDuration, Item.Duration); + Item.SQL := Copy(Raw, p+1, Length(Raw)); + Add(Item); + except + on E:Exception do begin + MainForm.LogSQL(E.ClassName+': '+E.Message+' Sessionpath: '+AppSettings.SessionPath+' Valuename: '+ValueNames[i]+' Raw: '+Raw, lcDebug); + Item.Free; + Break; + end; + end; + end; + end; + // Sort by date + Sort; + ValueNames.Free; + AppSettings.ResetPath; +end;} + + +{function TQueryHistoryItemComparer.Compare(const Left, Right: TQueryHistoryItem): Integer; +begin + // Simple sort method for a TDBObjectList + if Left.Time > Right.Time then + Result := -1 + else if Left.Time = Right.Time then + Result := 0 + else + Result := 1; +end;} + + +{ TListBindParam } + +constructor TListBindParam.Create; +begin + inherited Create(TBindParamComparer.Create); + FPairDelimiter := '|'; + FItemDelimiter := '~'; +end; + + +procedure TListBindParam.CleanToKeep; +var + Index: Integer; +begin + // Check for each parameter if there is to be deleted + for Index:=Count-1 downto 0 do begin + if Items[Index].Keep = False then + Self.Delete(Index); + end; + + // Set False all items for the next call + for Index:=0 to Count-1 do begin + Items[Index].Keep := False; + end; +end; + + +function TListBindParam.GetAsText: String; +var + Param: TBindParam; + Lines: TStringList; +begin + // Return params as storable text + Lines := TStringList.Create; + for Param in Self do begin + Lines.Add(Param.Name + FPairDelimiter + Param.Value); + end; + Result := Implode(FItemDelimiter, Lines); + Lines.Free; +end; + +procedure TListBindParam.SetAsText(Input: String); +var + Param: TBindParam; + Lines, Pair: TStringList; + Line: String; +begin + // Restore params from text + // See #689 + Lines := Explode(FItemDelimiter, Input); + for Line in Lines do begin + Pair := Explode(FPairDelimiter, Line); + if Pair.Count >= 2 then begin + Param := TBindParam.Create; + Param.Name := Pair[0]; + Param.Value := Pair[1]; + Add(Param); + end; + Pair.Free; + end; + Lines.Free; +end; + + +{ TBindParamComparer } + +function TBindParamComparer.Compare(constref Left, Right: TBindParam): Integer; +begin + // Simple sort method for a TDBObjectList + Result := CompareText(Left.Name, Right.Name); +end; + +end. + +