Files
HeidiSQL/source/apphelpers.pas
2025-03-09 19:28:01 +01:00

4636 lines
170 KiB
ObjectPascal
Raw Blame History

unit apphelpers;
{$mode delphi}{$H+}
interface
uses
Classes, SysUtils, Generics.Collections, Generics.Defaults, Controls, RegExpr, Math, FileUtil,
StrUtils, Graphics, GraphUtil, LCLIntf, Forms, Clipbrd, Process, ActnList, Menus, Dialogs,
Character, DateUtils, laz.VirtualTrees, SynEdit,
dbconnection, dbstructures;
type
// Sync with main branch and Delphi structures
TSynMemo = TSynEdit;
TVirtualStringTree = TLazVirtualStringTree;
TExtFileSaveDialog = class(TSaveDialog);
TImageIndex = Integer;
PInt = ^Integer;
TProgressBarState = (pbsNormal, pbsError, pbsPaused);
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;
FLogMsg: String;
FLogCategory: TDBLogCategory;
procedure BeforeQuery;
procedure AfterQuery;
procedure BatchFinished;
procedure Log;
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: TLazVirtualStringTree; idx: Int64; ParentNode: PVirtualNode): PVirtualNode;
function SelectNode(VT: TLazVirtualStringTree; idx: Int64; ParentNode: PVirtualNode=nil): Boolean; overload;
function SelectNode(VT: TLazVirtualStringTree; 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: TLazVirtualStringTree; CurrentNode: PVirtualNode; Selected: Boolean=False): PVirtualNode;
function GetPreviousNode(Tree: TLazVirtualStringTree; 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: TLazVirtualStringTree; 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<TComponent>);
//function WebColorStrToColorDef(WebColor: string; Default: TColor): TColor;
function UserAgent(OwnerComponent: TComponent): String;
function CodeIndent(Steps: Integer=1): String;
function EscapeHotkeyPrefix(Text: String): String;
function GetFileNameWithoutExtension(Filename: String): String;
function GetCommandLine: String;
function GetDynLibExtension: 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] := '<27>';
end else begin
Result := Copy(Result, Length(Result)-MaxLen, Length(Result));
Result := '<27>' + 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, '&', '&amp;', [rfReplaceAll]);
result := StringReplace(result, '<', '&lt;', [rfReplaceAll]);
result := StringReplace(result, '>', '&gt;', [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
}
function _GetFileSize(Filename: String): Int64;
begin
Result := FileSize(Filename);
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));}
Beep;
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<66>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;
{***
Execute system command
Don't use for opening URL
}
procedure ShellExec(cmd: String; path: String=''; params: String=''; RunHidden: Boolean=False);
var
Msg: String;
ShowOptions: TShowWindowOptions;
ProcessResult: String;
begin
if ExecRegExprI('^https?\://', cmd) then begin
LCLIntf.OpenURL(cmd);
Exit;
end;
if RunHidden then
ShowOptions := swoHIDE
else
ShowOptions := swoNone;
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);
Process.RunCommandInDir(path, cmd, [params], ProcessResult, [], ShowOptions);
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: TStringList;
begin
Writer := TStringList.Create;
Writer.Text := Text;
Writer.SaveToFile(Filename, Encoding);
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
Result := TEncoding.UTF8
{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 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 <> laz.VirtualTrees.isNone then
VT.OnIncrementalSearch := Mainform.AnyGridIncrementalSearch;
//VT.OnStartOperation := Mainform.AnyGridStartOperation;
//VT.OnEndOperation := Mainform.AnyGridEndOperation;
end;
function GetTextHeight(Font: TFont): Integer;
begin
Result := Font.GetTextHeight('<27>y');
end;
function ColorAdjustBrightness(Col: TColor; Shift: SmallInt): TColor;
var
H, L, S: 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
ColorToHLS(Col, H, L, S);
if (L < 128) and (Shift < 0) then
Shift := Abs(Shift)
else if (L > 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: TLazVirtualStringTree; 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: TLazVirtualStringTree; 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: TLazVirtualStringTree; 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: TLazVirtualStringTree; 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
// Windows-only. Prefer "ssShift in GetKeyShiftState"
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: TLazVirtualStringTree; 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 IsLetter(cap[i]) 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;
Dialog := TTaskDialog.Create(nil);
Dialog.Flags := [tfEnableHyperlinks, tfAllowDialogCancellation];
Dialog.CommonButtons := [];
// 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;
Dialog.Text := Msg;
// Main icon, and footer link
case DlgType of
mtWarning:
Dialog.MainIcon := tdiWarning;
mtError: begin
Dialog.MainIcon := tdiError;
Dialog.FooterText := FooterText;
Dialog.FooterIcon := tdiInformation;
end;
mtInformation:
Dialog.MainIcon := tdiInformation;
mtConfirmation, mtCustom: begin
Dialog.Flags := Dialog.Flags + [tfUseHiconMain];
Dialog.MainIcon := tdiQuestion;
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 := True; //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;
//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\=]\"([^\"]+)\"';
if rx.Exec(CommandLine) then
Result := rx.Match[2]
else begin
rx.Expression := '\s(\-'+ShortName+'|\-\-'+LongName+')[\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
Result := 64;
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;
{$IfDef WINDOWS}
var
NTHandle: THandle;
wine_nt_to_unix_file_name: procedure(p1:pointer; p2:pointer); stdcall;
{$EndIf}
begin
{$IfDef WINDOWS}
// 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;
{$EndIf}
{$IfDef LINUX}
IsWineStored := 0;
{$EndIf}
Result := IsWineStored = 1;
end;
function DirSep: Char;
begin
Result := DirectorySeparator;
end;
procedure FindComponentInstances(BaseForm: TComponent; ClassType: TClass; var List: TObjectList<TComponent>);
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])
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
OS := 'Unknown';
{$IfDef LINUX}
OS := 'Linux';
{$EndIf}
{$IfDef WINDOWS}
OS := 'Windows';
{$EndIf}
{$IfDef DARWIN}
OS := 'macOS';
{$EndIf}
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, Menus.cHotkeyPrefix, Menus.cHotkeyPrefix + Menus.cHotkeyPrefix, [rfReplaceAll]);
end;
function GetFileNameWithoutExtension(Filename: String): String;
var
LastDotPos: Integer;
begin
Result := ExtractFileName(Filename);
LastDotPos := Result.LastIndexOf('.');
if LastDotPos > -1 then
Result := Result.Substring(0, LastDotPos+1);
end;
function GetCommandLine: String;
var
i: Integer;
begin
Result := '';
for i:=0 to ParamCount do begin
Result := Result + ParamStr(i) + ' ';
end;
Result := Trim(Result);
end;
function GetDynLibExtension: String;
begin
Result :=
{$IFDEF WINDOWS}'dll'{$EndIf}
{$IFDEF LINUX}'so'{$EndIf}
{$IFDEF MACOS}'dylib'{$EndIf}
;
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(BeforeQuery);
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
LogFromOutside(f_('Reached maximum number of result tabs (%d). To display more results, increase setting in Preferences > SQL', [AppSettings.ReadInt(asMaxQueryResults)]),
lcInfo
);
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(AfterQuery);
//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(BatchFinished);
end;
procedure TQueryThread.BeforeQuery;
begin
//MainForm.BeforeQueryExecution(Self);
end;
procedure TQueryThread.LogFromThread(Msg: String; Category: TDBLogCategory);
begin
FLogMsg := Msg;
FLogCategory := Category;
Queue(Log);
end;
procedure TQueryThread.Log;
begin
//FConnection.OnLog(FLogMsg, FLogCategory, FConnection);
end;
procedure TQueryThread.AfterQuery;
begin
//MainForm.AfterQueryExecution(Self);
end;
procedure TQueryThread.BatchFinished;
begin
//MainForm.FinishedQueryExecution(Self);
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
Beep;
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(GetProcessId)+'\';
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'+DirectorySeparator+'%session'+DirectorySeparator+'%db'+DirectorySeparator+'%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(NoteLineBackground));
InitSetting(asLogsqlnum, 'logsqlnum', 300);
InitSetting(asLogsqlwidth, 'logsqlwidth', 2000);
InitSetting(asSessionLogsDirectory, 'SessionLogsDirectory', 0, False, DirnameUserAppData + 'Sessionlogs' + DirectorySeparator);
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' + DirectorySeparator;
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, True);
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 := True; //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 := True; //FRegistry.ValueExists(ValueName);
end;
function TAppSettings.SessionPathExists(SessionPath: String): Boolean;
begin
Result := True; //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 true {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 := GetAppConfigDir(False);
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 := DirnameUserAppData;
// 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' + DirectorySeparator
end else begin
Result := DirnameUserAppData + 'Backups' + DirectorySeparator;
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' + DirectorySeparator
end else begin
Result := DirnameUserAppData + 'Highlighters' + DirectorySeparator;
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.